From e3ec10a94f1c6ed5323e5bf62c1dd5d7f09648ec Mon Sep 17 00:00:00 2001 From: Ralf Aron Date: Sat, 24 Feb 2024 19:32:34 +0100 Subject: [PATCH] feat: WebDAV endpoint support --- .vscode/launch.json | 2 +- .../src/lib/aas-tree/aas-tree-search.ts | 4 + .../src/lib/aas-tree/aas-tree.component.ts | 13 - .../src/lib/aas-tree/aas-tree.state.ts | 2 +- .../aas-lib/src/test/assets/test-document.ts | 2 +- .../aas-lib/src/test/template.service.spec.ts | 1 - projects/aas-portal/src/app/configuration.ts | 45 - .../src/app/dashboard/dashboard.service.ts | 2 +- .../add-endpoint-form.component.ts | 89 +- .../src/app/start/start.component.ts | 2 +- .../aas-portal/src/assets/i18n/de-de.json | 6 +- .../aas-portal/src/assets/i18n/en-us.json | 6 +- .../src/test/aas/new-element-command.spec.ts | 51 +- .../src/test/assets/test-document.ts | 2 +- .../start/add-endpoint-form.component.spec.ts | 67 +- .../src/app/aas-index/aas-index-factory.ts | 2 +- .../src/app/aas-index/lowdb/lowdb-query.ts | 2 +- .../src/app/aas-provider/aas-provider.ts | 27 +- .../aas-provider/aas-resource-scan-factory.ts | 36 +- .../src/app/aas-provider/aasx-server-scan.ts | 10 +- .../src/app/aas-provider/directory-scan.ts | 6 +- .../src/app/aas-provider/parallel.ts | 2 +- .../aas-server/src/app/application-info.ts | 2 +- ...-user-storage.ts => local-user-storage.ts} | 14 +- .../src/app/auth/user-storage-factory.ts | 4 +- projects/aas-server/src/app/configuration.ts | 79 +- .../app/controller/endpoints-controller.ts | 14 +- projects/aas-server/src/app/convert.ts | 9 + .../app/file-storage/file-storage-provider.ts | 3 + .../app/file-storage/local-file-storage.ts | 2 +- .../src/app/file-storage/webdav-storage.ts | 12 +- .../src/app/live/http/http-subscription.ts | 4 +- .../src/app/logging/logger-factory.ts | 2 +- .../src/app/packages/aas-resource-factory.ts | 89 +- .../aas-server-package.ts} | 8 +- .../aas-server-v0.ts} | 4 +- .../aas-server-v3.ts} | 6 +- .../aas-server.ts} | 6 +- .../aasx-directory.ts | 2 +- .../aasx-package.ts | 30 +- .../src/app/packages/json-reader.ts | 9 +- .../src/app/packages/json-writer.ts | 2 +- .../src/app/packages/xml-reader-v1.ts | 718 +++ .../aas-server/src/app/packages/xml-reader.ts | 699 ++- .../src/app/packages/xml-reader_v2.ts | 718 +++ .../src/app/template/template-storage.ts | 1 - .../src/app/types/html-document-element.ts | 3 + projects/aas-server/src/app/variable.ts | 2 +- .../test/aas-index/lowdb/lowdb-index.spec.ts | 2 +- .../test/aas-index/lowdb/lowdb-query.spec.ts | 2 +- .../test/aas-provider/aas-provider.spec.ts | 2 +- .../src/test/application-info.spec.ts | 2 +- .../src/test/assets/aas-example-v3.xml | 5302 +++++++++++++++++ .../aas-server/src/test/assets/test-db.json | 14 +- ...age.spec.ts => local-user-storage.spec.ts} | 33 +- .../aas-server/src/test/configuration.spec.ts | 82 +- .../controller/containers-controller.spec.ts | 2 +- .../controller/endpoints-controller.spec.ts | 4 +- .../controller/templates-controller.spec.ts | 2 +- .../file-storage/local-file-storage.spec.ts | 2 +- .../test/live/http/http-subscription.spec.ts | 8 +- .../aas-server-package.spec.ts} | 12 +- .../aas-server-v0.spec.ts} | 10 +- .../aas-server-v3.spec.ts} | 8 +- .../aasx-package.spec.ts | 5 +- .../src/test/packages/json-reader.spec.ts | 2 +- .../src/test/packages/xml-reader-v1.spec.ts | 68 + .../src/test/packages/xml-reader.spec.ts | 68 +- .../test/template/template-storage.spec.ts | 1 - projects/common/src/lib/aas.ts | 4 +- projects/common/src/lib/index.ts | 53 + projects/common/src/lib/types.ts | 5 +- projects/common/src/test/index.spec.ts | 46 + 73 files changed, 7831 insertions(+), 729 deletions(-) delete mode 100644 projects/aas-portal/src/app/configuration.ts rename projects/aas-server/src/app/auth/{locale-user-storage.ts => local-user-storage.ts} (92%) rename projects/aas-server/src/app/packages/{aasx-server/aasx-server-package.ts => aas-server/aas-server-package.ts} (94%) rename projects/aas-server/src/app/packages/{aasx-server/aasx-server-v0.ts => aas-server/aas-server-v0.ts} (98%) rename projects/aas-server/src/app/packages/{aasx-server/aasx-server-v3.ts => aas-server/aas-server-v3.ts} (98%) rename projects/aas-server/src/app/packages/{aasx-server/aasx-server.ts => aas-server/aas-server.ts} (95%) rename projects/aas-server/src/app/packages/{aasx-directory => file-system}/aasx-directory.ts (99%) rename projects/aas-server/src/app/packages/{aasx-directory => file-system}/aasx-package.ts (88%) create mode 100644 projects/aas-server/src/app/packages/xml-reader-v1.ts create mode 100644 projects/aas-server/src/app/packages/xml-reader_v2.ts create mode 100644 projects/aas-server/src/app/types/html-document-element.ts create mode 100644 projects/aas-server/src/test/assets/aas-example-v3.xml rename projects/aas-server/src/test/auth/{locale-user-storage.spec.ts => local-user-storage.spec.ts} (92%) rename projects/aas-server/src/test/packages/{aasx-server/aasx-server-package.spec.ts => aas-server/aas-server-package.spec.ts} (78%) rename projects/aas-server/src/test/packages/{aasx-server/aasx-server-v0.spec.ts => aas-server/aas-server-v0.spec.ts} (94%) rename projects/aas-server/src/test/packages/{aasx-server/aasx-server-v3.spec.ts => aas-server/aas-server-v3.spec.ts} (97%) rename projects/aas-server/src/test/packages/{aasx-server => aas-server}/aasx-package.spec.ts (90%) create mode 100644 projects/aas-server/src/test/packages/xml-reader-v1.spec.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 9fd48ed0..e40b1d65 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -31,7 +31,7 @@ "USER_STORAGE": "mongodb://localhost:27017/aasportal-users", "TEMPLATE_STORAGE": "http://localhost:8080/templates", "AAS_INDEX": "mysql://localhost:3306", - "ENDPOINTS": "[\"file:///endpoints/samples?name=Samples\",\"http://localhost:5001?name:AASX%20Server\"]", + "ENDPOINTS": "[\"file:///endpoints/samples?name=Samples\",\"http://localhost:5001?name=AASX%20Server\", \"http://localhost:8080/endpoints/idta?name=IDTA\"]", } }, { diff --git a/projects/aas-lib/src/lib/aas-tree/aas-tree-search.ts b/projects/aas-lib/src/lib/aas-tree/aas-tree-search.ts index 01c27239..1802c8a9 100644 --- a/projects/aas-lib/src/lib/aas-tree/aas-tree-search.ts +++ b/projects/aas-lib/src/lib/aas-tree/aas-tree-search.ts @@ -320,6 +320,10 @@ export class AASTreeSearch { } case 'ReferenceElement': { const referenceElement = referable as aas.ReferenceElement; + if (!referenceElement.value) { + return false; + } + return referenceElement.value.keys.some(key => this.containsString(key.value, value as string)); } case 'MultiLanguageProperty': { diff --git a/projects/aas-lib/src/lib/aas-tree/aas-tree.component.ts b/projects/aas-lib/src/lib/aas-tree/aas-tree.component.ts index a400b774..5e8f2708 100644 --- a/projects/aas-lib/src/lib/aas-tree/aas-tree.component.ts +++ b/projects/aas-lib/src/lib/aas-tree/aas-tree.component.ts @@ -22,11 +22,9 @@ import { AASDocument, convertToString, toLocale, - EndpointType, selectSubmodel, getIdShortPath, mimeTypeToExtension, - AASEndpointType, } from 'common'; import { AASTreeRow, AASTreeFeatureState } from './aas-tree.state'; @@ -513,17 +511,6 @@ export class AASTreeComponent implements OnInit, OnChanges, OnDestroy { } } - private toEndpointType(type: AASEndpointType): EndpointType { - switch (type) { - case 'OpcuaServer': - return 'opc'; - case 'AasxDirectory': - return 'file'; - default: - return 'http'; - } - } - private stop(): void { if (this.webSocketSubject) { this.webSocketSubject.unsubscribe(); diff --git a/projects/aas-lib/src/lib/aas-tree/aas-tree.state.ts b/projects/aas-lib/src/lib/aas-tree/aas-tree.state.ts index f35c9f97..5cd4fc45 100644 --- a/projects/aas-lib/src/lib/aas-tree/aas-tree.state.ts +++ b/projects/aas-lib/src/lib/aas-tree/aas-tree.state.ts @@ -279,7 +279,7 @@ class TreeInitialize { return blob.contentType ? `${blob.idShort}${extension}` : '-'; } case 'ReferenceElement': - return (referable as aas.ReferenceElement).value.keys.map(item => item.value).join('/'); + return (referable as aas.ReferenceElement).value?.keys.map(item => item.value).join('/'); case 'RelationshipElement': return this.getRelationshipElementValue(referable as aas.RelationshipElement); case 'MultiLanguageProperty': diff --git a/projects/aas-lib/src/test/assets/test-document.ts b/projects/aas-lib/src/test/assets/test-document.ts index 14bd68b4..0a750c88 100644 --- a/projects/aas-lib/src/test/assets/test-document.ts +++ b/projects/aas-lib/src/test/assets/test-document.ts @@ -13,7 +13,7 @@ export function createContainer(url: string, documents: AASDocument[]): AASConta documents: documents, url: url, name: url, - type: 'AasxDirectory' + type: 'FileSystem' }; } diff --git a/projects/aas-lib/src/test/template.service.spec.ts b/projects/aas-lib/src/test/template.service.spec.ts index f2411316..917e5e1f 100644 --- a/projects/aas-lib/src/test/template.service.spec.ts +++ b/projects/aas-lib/src/test/template.service.spec.ts @@ -35,7 +35,6 @@ describe('TemplateService', () => { idShort: '', modelType: 'Property', endpoint: { type: 'file', address: 'property.json' }, - template: null, }; const url = `/api/v1/templates`; diff --git a/projects/aas-portal/src/app/configuration.ts b/projects/aas-portal/src/app/configuration.ts deleted file mode 100644 index 61d6b5bd..00000000 --- a/projects/aas-portal/src/app/configuration.ts +++ /dev/null @@ -1,45 +0,0 @@ -/****************************************************************************** - * - * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, - * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft - * zur Foerderung der angewandten Forschung e.V. - * - *****************************************************************************/ - -import { AASEndpointType } from 'common'; - -/** - * Gets the endpoint name from the specified URL. - * @param url The endpoint URL. - * @returns The name. - */ -export function getEndpointName(url: string | URL): string { - if (typeof url === 'string') { - url = new URL(url); - } - - return url.searchParams.get('name') ?? url.href.split('?')[0]; -} - -/** - * Gets the endpoint type from the specified URL. - * @param url The URL. - * @returns The endpoint type. - */ -export function getEndpointType(url: string | URL): AASEndpointType { - if (typeof url === 'string') { - url = new URL(url); - } - - switch (url.protocol) { - case 'file:': - return 'AasxDirectory'; - case 'http:': - case 'https:': - return (url.searchParams.get('type') as AASEndpointType) ?? 'AasxServer'; - case 'opc.tcp:': - return 'OpcuaServer'; - default: - throw new Error(`Protocol "${url.protocol}" is not supported.`); - } -} diff --git a/projects/aas-portal/src/app/dashboard/dashboard.service.ts b/projects/aas-portal/src/app/dashboard/dashboard.service.ts index 51896c89..18ae2b05 100644 --- a/projects/aas-portal/src/app/dashboard/dashboard.service.ts +++ b/projects/aas-portal/src/app/dashboard/dashboard.service.ts @@ -380,7 +380,7 @@ export class DashboardService { switch (type) { case 'OpcuaServer': return 'opc'; - case 'AasxDirectory': + case 'FileSystem': return 'file'; default: return 'http'; diff --git a/projects/aas-portal/src/app/start/add-endpoint-form/add-endpoint-form.component.ts b/projects/aas-portal/src/app/start/add-endpoint-form/add-endpoint-form.component.ts index 563ec97b..776f5d1a 100644 --- a/projects/aas-portal/src/app/start/add-endpoint-form/add-endpoint-form.component.ts +++ b/projects/aas-portal/src/app/start/add-endpoint-form/add-endpoint-form.component.ts @@ -29,8 +29,8 @@ export class AddEndpointFormComponent { ) { this.items = [ { - name: this.translate.instant('AASEndpointType.AasxServer'), - type: 'AasxServer', + name: this.translate.instant('AASEndpointType.AASServer'), + type: 'AASServer', value: 'http://', }, { @@ -39,8 +39,13 @@ export class AddEndpointFormComponent { value: 'opc.tcp://', }, { - name: this.translate.instant('AASEndpointType.AasxDirectory'), - type: 'AasxDirectory', + name: this.translate.instant('AASEndpointType.WebDAV'), + type: 'WebDAV', + value: 'http://', + }, + { + name: this.translate.instant('AASEndpointType.FileSystem'), + type: 'FileSystem', value: 'file:///', }, ]; @@ -48,13 +53,13 @@ export class AddEndpointFormComponent { this.item = this.items[0]; } - public endpoints: string[] = []; + public endpoints: AASEndpoint[] = []; public messages: string[] = []; public name = ''; - public version = '3.0'; + public version = 'v3'; public readonly items: EndpointItem[]; @@ -95,8 +100,8 @@ export class AddEndpointFormComponent { this.messages.push(this.createMessage('ERROR_EMPTY_ENDPOINT_NAME')); name = undefined; } else { - for (const workspace of this.endpoints) { - if (workspace.toLocaleLowerCase() === name.toLocaleLowerCase()) { + for (const endpoint of this.endpoints) { + if (endpoint.name.toLocaleLowerCase() === name.toLocaleLowerCase()) { this.messages.push(this.createMessage('ERROR_ENDPOINT_ALREADY_EXIST', name)); name = undefined; break; @@ -110,22 +115,18 @@ export class AddEndpointFormComponent { private validateUrl(value: string): URL | undefined { try { const url = new URL(value); - switch (url.protocol) { - case 'opc.tcp:': - if (url.pathname === '//') { - throw new Error('Empty pathname.'); - } + switch (this.item.type) { + case 'AASServer': + this.validateAASServerEndpoint(url); + break; + case 'FileSystem': + this.validateFileSystemEndpoint(url); break; - case 'http:': + case 'OpcuaServer': + this.validateOpcuaEndpoint(url); break; - case 'file:': - if (url.hostname !== '') { - throw new Error(`Invalid host name ${url.hostname}.`); - } - - if (url.pathname === '/') { - throw new Error('Empty pathname.'); - } + case 'WebDAV': + this.validateWebDAVEndpoint(url); break; } @@ -139,4 +140,48 @@ export class AddEndpointFormComponent { private createMessage(id: string, ...args: unknown[]): string { return stringFormat(this.translate.instant(id), args); } + + private validateAASServerEndpoint(url: URL): void { + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + throw new Error('Protocol "http:" or "https:" expected.'); + } + + if (url.pathname !== '/') { + throw new Error(`Unexpected pathname "${url.pathname}".`); + } + } + + private validateFileSystemEndpoint(url: URL): void { + if (url.protocol !== 'file:') { + throw new Error('Protocol "file:" expected'); + } + + if (url.hostname !== '') { + throw new Error(`Invalid host name ${url.hostname}.`); + } + + if (url.pathname === '/') { + throw new Error('Empty pathname.'); + } + } + + private validateOpcuaEndpoint(url: URL): void { + if (url.protocol !== 'opc.tcp:') { + throw new Error('Protocol "opc.tcp:" expected.'); + } + + if (url.pathname === '//' || url.pathname === '/') { + throw new Error('Empty pathname.'); + } + } + + private validateWebDAVEndpoint(url: URL): void { + if (url.protocol !== 'http:' && url.protocol !== 'https:') { + throw new Error('Protocol "http:" or "https:" expected.'); + } + + if (url.pathname === '/') { + throw new Error('Empty pathname.'); + } + } } diff --git a/projects/aas-portal/src/app/start/start.component.ts b/projects/aas-portal/src/app/start/start.component.ts index 74da7dda..5dd460d1 100644 --- a/projects/aas-portal/src/app/start/start.component.ts +++ b/projects/aas-portal/src/app/start/start.component.ts @@ -185,7 +185,7 @@ export class StartComponent implements OnDestroy, AfterViewInit { mergeMap(() => this.api.getEndpoints()), map(endpoints => { const modalRef = this.modal.open(AddEndpointFormComponent, { backdrop: 'static' }); - modalRef.componentInstance.workspaces = endpoints; + modalRef.componentInstance.endpoints = endpoints; return modalRef; }), mergeMap(modalRef => from>(modalRef.result)), diff --git a/projects/aas-portal/src/assets/i18n/de-de.json b/projects/aas-portal/src/assets/i18n/de-de.json index 3e7c6274..97a3d862 100644 --- a/projects/aas-portal/src/assets/i18n/de-de.json +++ b/projects/aas-portal/src/assets/i18n/de-de.json @@ -163,9 +163,9 @@ "OPTION_BAR_VERTICAL_CHART": "Balkendiagramm vertikal", "OPTION_BAR_HORIZONTAL_CHART": "Balkendiagramm horizontal", "AASEndpointType": { - "AasxDirectory": "Verzeichnis", - "AasxServer": "AASX-Server", - "AASRegistry": "AAS-Registry", + "FileSystem": "Verzeichnis", + "AASServer": "AAS-Server", + "WebDAV": "Cloud-Server", "OpcuaServer": "OPC-UA-Server" }, "CustomerFeedback": { diff --git a/projects/aas-portal/src/assets/i18n/en-us.json b/projects/aas-portal/src/assets/i18n/en-us.json index 3aa92e15..2825e8e4 100644 --- a/projects/aas-portal/src/assets/i18n/en-us.json +++ b/projects/aas-portal/src/assets/i18n/en-us.json @@ -163,9 +163,9 @@ "OPTION_BAR_VERTICAL_CHART": "Vertical bar chart", "OPTION_BAR_HORIZONTAL_CHART": "Horizontal bar chart", "AASEndpointType": { - "AasxDirectory": "Directory", - "AasxServer": "AASX Server", - "AASRegistry": "AAS Registry", + "FileSystem": "File system", + "AASServer": "AAS server", + "WebDAV": "Cloud server", "OpcuaServer": "OPC UA Server" }, "CustomerFeedback": { diff --git a/projects/aas-portal/src/test/aas/new-element-command.spec.ts b/projects/aas-portal/src/test/aas/new-element-command.spec.ts index 09370169..91d5574e 100644 --- a/projects/aas-portal/src/test/aas/new-element-command.spec.ts +++ b/projects/aas-portal/src/test/aas/new-element-command.spec.ts @@ -28,14 +28,12 @@ describe('NewElementCommand', function () { TestBed.configureTestingModule({ declarations: [], - providers: [ - ], + providers: [], imports: [ - StoreModule.forRoot( - { - aas: aasReducer - }), - ] + StoreModule.forRoot({ + aas: aasReducer, + }), + ], }); store = TestBed.inject(Store); @@ -47,25 +45,34 @@ describe('NewElementCommand', function () { }); it('can be executed', function (done: DoneFn) { - store.select(state => state.aas.document).pipe(first()).subscribe(document => { - const element = selectElement(document!.content!, 'TechnicalData'); - expect(element).toBeDefined(); - done(); - }); + store + .select(state => state.aas.document) + .pipe(first()) + .subscribe(document => { + const element = selectElement(document!.content!, 'TechnicalData'); + expect(element).toBeDefined(); + done(); + }); }); it('can be undone/redone', function (done: DoneFn) { command.undo(); - store.select(state => state.aas.document).pipe(first()).subscribe(document => { - const element = selectElement(document!.content!, 'TechnicalData'); - expect(element).toBeUndefined(); - }); + store + .select(state => state.aas.document) + .pipe(first()) + .subscribe(document => { + const element = selectElement(document!.content!, 'TechnicalData'); + expect(element).toBeUndefined(); + }); command.redo(); - store.select(state => state.aas.document).pipe(first()).subscribe(document => { - const element = selectElement(document!.content!, 'TechnicalData'); - expect(element).toBeDefined(); - done(); - }); + store + .select(state => state.aas.document) + .pipe(first()) + .subscribe(document => { + const element = selectElement(document!.content!, 'TechnicalData'); + expect(element).toBeDefined(); + done(); + }); }); -}); \ No newline at end of file +}); diff --git a/projects/aas-portal/src/test/assets/test-document.ts b/projects/aas-portal/src/test/assets/test-document.ts index d6a28861..e0688f93 100644 --- a/projects/aas-portal/src/test/assets/test-document.ts +++ b/projects/aas-portal/src/test/assets/test-document.ts @@ -13,7 +13,7 @@ export function createContainer(url: string, documents: AASDocument[]): AASConta documents: documents, url: url, name: url, - type: 'AasxServer' + type: 'AASServer' }; } diff --git a/projects/aas-portal/src/test/start/add-endpoint-form.component.spec.ts b/projects/aas-portal/src/test/start/add-endpoint-form.component.spec.ts index 6c44f4d0..3aba5eb0 100644 --- a/projects/aas-portal/src/test/start/add-endpoint-form.component.spec.ts +++ b/projects/aas-portal/src/test/start/add-endpoint-form.component.spec.ts @@ -24,12 +24,8 @@ describe('AddEndpointFormComponent', () => { beforeEach(() => { TestBed.configureTestingModule({ - declarations: [ - AddEndpointFormComponent - ], - providers: [ - NgbActiveModal - ], + declarations: [AddEndpointFormComponent], + providers: [NgbActiveModal], imports: [ CommonModule, FormsModule, @@ -37,10 +33,10 @@ describe('AddEndpointFormComponent', () => { TranslateModule.forRoot({ loader: { provide: TranslateLoader, - useClass: TranslateFakeLoader - } - }) - ] + useClass: TranslateFakeLoader, + }, + }), + ], }); fixture = TestBed.createComponent(AddEndpointFormComponent); @@ -59,9 +55,9 @@ describe('AddEndpointFormComponent', () => { it('submits endpoint Name: "My endpoint", URL: "file:///my-endpoint"', function () { let endpoint: AASEndpoint | undefined; - spyOn(modal, 'close').and.callFake((result) => endpoint = result); + spyOn(modal, 'close').and.callFake(result => (endpoint = result)); - component.item = component.items[2]; + component.item = component.items[3]; component.name = 'My endpoint'; component.item.value = 'file:///my-endpoint'; @@ -69,14 +65,14 @@ describe('AddEndpointFormComponent', () => { expect(modal.close).toHaveBeenCalled(); expect(endpoint?.name).toEqual('My endpoint'); expect(endpoint?.url).toEqual('file:///my-endpoint'); - expect(endpoint?.type).toEqual('AasxDirectory'); + expect(endpoint?.type).toEqual('FileSystem'); }); it('submits AAS endpoint Name: "My endpoint", URL: "file:///a\\b\\my-endpoint"', function () { let endpoint: AASEndpoint | undefined; - spyOn(modal, 'close').and.callFake((result) => endpoint = result); + spyOn(modal, 'close').and.callFake(result => (endpoint = result)); - component.item = component.items[2]; + component.item = component.items[3]; component.name = 'My endpoint'; component.item.value = 'file:///a\\b\\my-endpoint'; @@ -84,13 +80,13 @@ describe('AddEndpointFormComponent', () => { expect(modal.close).toHaveBeenCalled(); expect(endpoint?.name).toEqual('My endpoint'); expect(endpoint?.url).toEqual('file:///a/b/my-endpoint'); - expect(endpoint?.type).toEqual('AasxDirectory'); + expect(endpoint?.type).toEqual('FileSystem'); }); it('ignores AAS endpoint: Name: "", URL: "file:///my-endpoint"', function () { spyOn(modal, 'close'); - component.item = component.items[2]; + component.item = component.items[3]; component.name = ''; component.item.value = 'file:///my-endpoint'; @@ -102,7 +98,7 @@ describe('AddEndpointFormComponent', () => { it('ignores AAS endpoint Name: "My endpoint", URL: "file:///"', function () { spyOn(modal, 'close'); - component.item = component.items[2]; + component.item = component.items[3]; component.name = 'My endpoint'; component.item.value = 'file:///'; @@ -111,18 +107,18 @@ describe('AddEndpointFormComponent', () => { expect(component.messages.length > 0).toBeTrue(); }); - it('submits AAS endpoint Name: "I4AAS Server", URL: "opc.tcp://172.16.160.178:30001/I4AASServer"', function () { + it('submits AAS endpoint Name: "I4AAS Server", URL: "opc.tcp://localhost:30001/I4AASServer"', function () { let endpoint: AASEndpoint | undefined; - spyOn(modal, 'close').and.callFake((result) => endpoint = result); + spyOn(modal, 'close').and.callFake(result => (endpoint = result)); component.item = component.items[1]; component.name = 'I4AAS Server'; - component.item.value = 'opc.tcp://172.16.160.178:30001/I4AASServer'; + component.item.value = 'opc.tcp://localhost:30001/I4AASServer'; form.dispatchEvent(new Event('submit')); expect(modal.close).toHaveBeenCalled(); expect(endpoint?.name).toEqual('I4AAS Server'); - expect(endpoint?.url).toEqual('opc.tcp://172.16.160.178:30001/I4AASServer'); + expect(endpoint?.url).toEqual('opc.tcp://localhost:30001/I4AASServer'); expect(endpoint?.type).toEqual('OpcuaServer'); }); @@ -142,18 +138,33 @@ describe('AddEndpointFormComponent', () => { expect(component.messages.length > 0).toBeTrue(); }); - it('submits AASX server Name: "AASX Server", URL: "http://172.16.160.188:50001/"', function () { + it('submits AASX server Name: "AASX Server", URL: "http://localhost:50001/"', function () { let endpoint: AASEndpoint | undefined; - spyOn(modal, 'close').and.callFake((result) => endpoint = result); + spyOn(modal, 'close').and.callFake(result => (endpoint = result)); component.item = component.items[0]; component.name = 'AASX Server'; - component.item.value = 'http://172.16.160.188:50001/'; + component.item.value = 'http://localhost:50001/'; form.dispatchEvent(new Event('submit')); expect(modal.close).toHaveBeenCalled(); expect(endpoint?.name).toEqual('AASX Server'); - expect(endpoint?.url).toEqual('http://172.16.160.188:50001/'); - expect(endpoint?.type).toEqual('AasxServer'); + expect(endpoint?.url).toEqual('http://localhost:50001/'); + expect(endpoint?.type).toEqual('AASServer'); + }); + + it('submits WebDAV server Name: "WebDAV", URL: "http://localhost:8080/root/folder"', function () { + let endpoint: AASEndpoint | undefined; + spyOn(modal, 'close').and.callFake(result => (endpoint = result)); + + component.item = component.items[2]; + component.name = 'WebDAV Server'; + component.item.value = 'http://localhost:8080/root/folder'; + + form.dispatchEvent(new Event('submit')); + expect(modal.close).toHaveBeenCalled(); + expect(endpoint?.name).toEqual('WebDAV Server'); + expect(endpoint?.url).toEqual('http://localhost:8080/root/folder'); + expect(endpoint?.type).toEqual('WebDAV'); }); -}); \ No newline at end of file +}); diff --git a/projects/aas-server/src/app/aas-index/aas-index-factory.ts b/projects/aas-server/src/app/aas-index/aas-index-factory.ts index 0f5d1519..adb5ec46 100644 --- a/projects/aas-server/src/app/aas-index/aas-index-factory.ts +++ b/projects/aas-server/src/app/aas-index/aas-index-factory.ts @@ -7,7 +7,7 @@ *****************************************************************************/ import { DependencyContainer } from 'tsyringe'; -import path from 'path'; +import path from 'path/posix'; import { JSONFile } from 'lowdb/node'; import { Low } from 'lowdb'; import { AASIndex } from './aas-index.js'; diff --git a/projects/aas-server/src/app/aas-index/lowdb/lowdb-query.ts b/projects/aas-server/src/app/aas-index/lowdb/lowdb-query.ts index 5834f010..fd7451fd 100644 --- a/projects/aas-server/src/app/aas-index/lowdb/lowdb-query.ts +++ b/projects/aas-server/src/app/aas-index/lowdb/lowdb-query.ts @@ -6,7 +6,7 @@ * *****************************************************************************/ -import { normalize } from 'path'; +import { normalize } from 'path/posix'; import { AASDocument, BaseValueType, AASQuery, AASQueryOperator, AASQueryValueType, OrExpression } from 'common'; import { LowDbDocument, LowDbElement, LowDbElementValueType } from './lowdb-types.js'; diff --git a/projects/aas-server/src/app/aas-provider/aas-provider.ts b/projects/aas-server/src/app/aas-provider/aas-provider.ts index 5bd9dc09..0b0e31fb 100644 --- a/projects/aas-server/src/app/aas-provider/aas-provider.ts +++ b/projects/aas-server/src/app/aas-provider/aas-provider.ts @@ -7,7 +7,7 @@ *****************************************************************************/ import { inject, singleton } from 'tsyringe'; -import { extname } from 'path'; +import { extname } from 'path/posix'; import Jimp from 'jimp'; import { Readable } from 'stream'; import { @@ -333,7 +333,7 @@ export class AASProvider { /** Only used for test. */ public async scanAsync(factory: AASResourceScanFactory): Promise { for (const endpoint of await this.index.getEndpoints()) { - if (endpoint.type === 'AasxDirectory') { + if (endpoint.type === 'FileSystem') { const documents = await factory.create(endpoint).scanAsync(); documents.forEach(async document => await this.index.add(document)); } @@ -511,13 +511,16 @@ export class AASProvider { private async collectDescendants(parent: AASDocument, nodes: AASDocument[]): Promise { const content = await this.getDocumentContentAsync(parent); for (const reference of this.whereReferenceElement(content.submodels)) { - const childId = reference.value.keys[0].value; - const child = - (await this.index.find(parent.endpoint, childId)) ?? (await this.index.find(undefined, childId)); - if (child) { - const node: AASDocument = { ...child, parent: { ...parent }, content: null }; - nodes.push(node); - await this.collectDescendants(node, nodes); + if (reference.value) { + const childId = reference.value.keys[0].value; + const child = + (await this.index.find(parent.endpoint, childId)) ?? (await this.index.find(undefined, childId)); + + if (child) { + const node: AASDocument = { ...child, parent: { ...parent }, content: null }; + nodes.push(node); + await this.collectDescendants(node, nodes); + } } } } @@ -530,7 +533,11 @@ export class AASProvider { for (const child of children) { if (child.modelType === 'ReferenceElement') { const value = child as aas.ReferenceElement; - if (value && value.value.keys.some(item => item.type === 'AssetAdministrationShell')) { + if ( + value && + value.value && + value.value.keys.some(item => item.type === 'AssetAdministrationShell') + ) { yield value; } } diff --git a/projects/aas-server/src/app/aas-provider/aas-resource-scan-factory.ts b/projects/aas-server/src/app/aas-provider/aas-resource-scan-factory.ts index 97d05ae3..11a34aaa 100644 --- a/projects/aas-server/src/app/aas-provider/aas-resource-scan-factory.ts +++ b/projects/aas-server/src/app/aas-provider/aas-resource-scan-factory.ts @@ -14,10 +14,10 @@ import { DirectoryScan } from './directory-scan.js'; import { AASXServerScan } from './aasx-server-scan.js'; import { OpcuaServerScan } from './opcua-server-scan.js'; import { OpcuaServer } from '../packages/opcua/opcua-server.js'; -import { AasxDirectory } from '../packages/aasx-directory/aasx-directory.js'; -import { AasxServer } from '../packages/aasx-server/aasx-server.js'; -import { AasxServerV3 } from '../packages/aasx-server/aasx-server-v3.js'; -import { AasxServerV0 } from '../packages/aasx-server/aasx-server-v0.js'; +import { AasxDirectory } from '../packages/file-system/aasx-directory.js'; +import { AASServer } from '../packages/aas-server/aas-server.js'; +import { AASServerV3 } from '../packages/aas-server/aas-server-v3.js'; +import { AASServerV0 } from '../packages/aas-server/aas-server-v0.js'; import { FileStorageProvider } from '../file-storage/file-storage-provider.js'; import { FileStorage } from '../file-storage/file-storage.js'; @@ -29,24 +29,26 @@ export class AASResourceScanFactory { ) {} public create(endpoint: AASEndpoint): AASResourceScan { - switch (new URL(endpoint.url).protocol) { - case 'http:': - case 'https': { - const version = endpoint.version ?? '3.0'; - let source: AasxServer; - if (version === '3.0') { - source = new AasxServerV3(this.logger, endpoint.url, endpoint.name); - } else if (version === '0.0') { - source = new AasxServerV0(this.logger, endpoint.url, endpoint.name); - } else { - throw new Error('Not implemented.'); + switch (endpoint.type) { + case 'AASServer': { + let source: AASServer; + switch (endpoint.version) { + case 'v0': + source = new AASServerV0(this.logger, endpoint.url, endpoint.name); + break; + case 'v3': + source = new AASServerV3(this.logger, endpoint.url, endpoint.name); + break; + default: + throw new Error('Not implemented.'); } return new AASXServerScan(this.logger, source); } - case 'opc.tcp:': + case 'OpcuaServer': return new OpcuaServerScan(this.logger, new OpcuaServer(this.logger, endpoint.url, endpoint.name)); - case 'file:': + case 'WebDAV': + case 'FileSystem': return new DirectoryScan( this.logger, new AasxDirectory( diff --git a/projects/aas-server/src/app/aas-provider/aasx-server-scan.ts b/projects/aas-server/src/app/aas-provider/aasx-server-scan.ts index ea2e6674..bd394691 100644 --- a/projects/aas-server/src/app/aas-provider/aasx-server-scan.ts +++ b/projects/aas-server/src/app/aas-provider/aasx-server-scan.ts @@ -8,15 +8,15 @@ import { AASDocument } from 'common'; import { Logger } from '../logging/logger.js'; -import { AasxServer } from '../packages/aasx-server/aasx-server.js'; -import { AasxServerPackage } from '../packages/aasx-server/aasx-server-package.js'; +import { AASServer } from '../packages/aas-server/aas-server.js'; +import { AASServerPackage } from '../packages/aas-server/aas-server-package.js'; import { AASResourceScan } from './aas-resource-scan.js'; export class AASXServerScan extends AASResourceScan { private readonly logger: Logger; - private readonly server: AasxServer; + private readonly server: AASServer; - public constructor(logger: Logger, server: AasxServer) { + public constructor(logger: Logger, server: AASServer) { super(); this.logger = logger; @@ -30,7 +30,7 @@ export class AASXServerScan extends AASResourceScan { const listAAS = await this.server.getShellsAsync(); for (const idShort of listAAS) { try { - const aasxPackage = new AasxServerPackage(this.logger, this.server, idShort); + const aasxPackage = new AASServerPackage(this.logger, this.server, idShort); const document = await aasxPackage.createDocumentAsync(); documents.push(document); this.emit('scanned', document); diff --git a/projects/aas-server/src/app/aas-provider/directory-scan.ts b/projects/aas-server/src/app/aas-provider/directory-scan.ts index 8e506547..a34a6577 100644 --- a/projects/aas-server/src/app/aas-provider/directory-scan.ts +++ b/projects/aas-server/src/app/aas-provider/directory-scan.ts @@ -8,9 +8,9 @@ import { AASDocument } from 'common'; import { Logger } from '../logging/logger.js'; -import { AasxPackage } from '../packages/aasx-directory/aasx-package.js'; -import { AasxDirectory } from '../packages/aasx-directory/aasx-directory.js'; -import { extname } from 'path'; +import { AasxPackage } from '../packages/file-system/aasx-package.js'; +import { AasxDirectory } from '../packages/file-system/aasx-directory.js'; +import { extname } from 'path/posix'; import { AASResourceScan } from './aas-resource-scan.js'; export class DirectoryScan extends AASResourceScan { diff --git a/projects/aas-server/src/app/aas-provider/parallel.ts b/projects/aas-server/src/app/aas-provider/parallel.ts index fd2693bd..bb477a06 100644 --- a/projects/aas-server/src/app/aas-provider/parallel.ts +++ b/projects/aas-server/src/app/aas-provider/parallel.ts @@ -11,7 +11,7 @@ import { EventEmitter } from 'events'; import { noop } from 'lodash-es'; import { Worker, SHARE_ENV } from 'worker_threads'; import fs from 'fs'; -import path from 'path'; +import path from 'path/posix'; import { ScanResultType, ScanResult } from './scan-result.js'; import { WorkerData } from './worker-data.js'; diff --git a/projects/aas-server/src/app/application-info.ts b/projects/aas-server/src/app/application-info.ts index 266ecc30..7d089158 100644 --- a/projects/aas-server/src/app/application-info.ts +++ b/projects/aas-server/src/app/application-info.ts @@ -6,7 +6,7 @@ * *****************************************************************************/ -import { isAbsolute, resolve } from 'path'; +import { isAbsolute, resolve } from 'path/posix'; import { Message, PackageInfo } from 'common'; import { Logger } from './logging/logger.js'; import { readFile } from 'fs/promises'; diff --git a/projects/aas-server/src/app/auth/locale-user-storage.ts b/projects/aas-server/src/app/auth/local-user-storage.ts similarity index 92% rename from projects/aas-server/src/app/auth/locale-user-storage.ts rename to projects/aas-server/src/app/auth/local-user-storage.ts index 9cdf1310..1d7fd15c 100644 --- a/projects/aas-server/src/app/auth/locale-user-storage.ts +++ b/projects/aas-server/src/app/auth/local-user-storage.ts @@ -7,15 +7,17 @@ *****************************************************************************/ import { inject, injectable } from 'tsyringe'; -import path from 'path'; +import { join } from 'path/posix'; +import { resolve } from 'path'; import fs from 'fs'; import { Cookie } from 'common'; import { UserStorage } from './user-storage.js'; import { UserData } from './user-data.js'; import { Logger } from '../logging/logger.js'; +import { slash } from '../convert.js'; @injectable() -export class LocaleUserStorage extends UserStorage { +export class LocalUserStorage extends UserStorage { private readonly usersDirectory: string; public constructor( @@ -24,7 +26,7 @@ export class LocaleUserStorage extends UserStorage { ) { super(); - this.usersDirectory = path.resolve(usersDirectory); + this.usersDirectory = slash(resolve(usersDirectory)); if (!fs.existsSync(this.usersDirectory)) { fs.mkdirSync(this.usersDirectory); @@ -117,7 +119,7 @@ export class LocaleUserStorage extends UserStorage { } private getCookiesFile(userId: string): string { - return path.join(this.usersDirectory, userId, 'cookies.json'); + return join(this.usersDirectory, userId, 'cookies.json'); } private async readCookies(path: string): Promise { @@ -137,10 +139,10 @@ export class LocaleUserStorage extends UserStorage { } private getUserFile(userId: string): string { - return path.join(this.usersDirectory, userId, 'user.json'); + return join(this.usersDirectory, userId, 'user.json'); } private getUserDir(userId: string): string { - return path.join(this.usersDirectory, userId); + return join(this.usersDirectory, userId); } } diff --git a/projects/aas-server/src/app/auth/user-storage-factory.ts b/projects/aas-server/src/app/auth/user-storage-factory.ts index f594bed4..e902f1ad 100644 --- a/projects/aas-server/src/app/auth/user-storage-factory.ts +++ b/projects/aas-server/src/app/auth/user-storage-factory.ts @@ -8,7 +8,7 @@ import { DependencyContainer } from 'tsyringe'; import { UserStorage } from './user-storage.js'; -import { LocaleUserStorage } from './locale-user-storage.js'; +import { LocalUserStorage } from './local-user-storage.js'; import { Variable } from '../variable.js'; import { MongoDBUserStorage } from './mongo-db-user-storage.js'; import { Logger } from '../logging/logger.js'; @@ -34,7 +34,7 @@ export class UserStorageFactory { } } - const storage = this.container.resolve(LocaleUserStorage); + const storage = this.container.resolve(LocalUserStorage); logger.info(`Using local user storage.`); return storage; } diff --git a/projects/aas-server/src/app/configuration.ts b/projects/aas-server/src/app/configuration.ts index 9565b4f7..4f67d4e3 100644 --- a/projects/aas-server/src/app/configuration.ts +++ b/projects/aas-server/src/app/configuration.ts @@ -6,91 +6,26 @@ * *****************************************************************************/ -import { AASEndpoint, AASEndpointType } from 'common'; +import { AASEndpoint, getEndpointName, getEndpointType } from 'common'; /** The AAS Server configuration. */ export interface AASServerConfiguration { endpoints: AASEndpoint[]; } -/** - * Gets the endpoint name from the specified URL. - * @param url The endpoint URL. - * @returns The name. - */ -export function getEndpointName(url: string | URL): string { - if (typeof url === 'string') { - url = new URL(url); - } - - return url.searchParams.get('name') ?? url.href.split('?')[0]; -} - -/** - * Gets the endpoint type from the specified URL. - * @param url The URL. - * @returns The endpoint type. - */ -export function getEndpointType(url: string | URL): AASEndpointType { - if (typeof url === 'string') { - url = new URL(url); - } - - switch (url.protocol) { - case 'file:': - return 'AasxDirectory'; - case 'http:': - case 'https:': - return (url.searchParams.get('type') as AASEndpointType) ?? 'AasxServer'; - case 'opc.tcp:': - return 'OpcuaServer'; - default: - throw new Error(`Protocol "${url.protocol}" is not supported.`); - } -} - -/** - * Creates an AAS container endpoint URL with required search parameters. - * @param address The AAS container endpoint address (URL). - * @param options The container endpoint options or name. - * @returns The endpoint URL as string. - */ -export function createEndpoint( - url: string, - name: string, - type: AASEndpointType = 'AasxServer', - version: string = '3.0', -): AASEndpoint { - return { url, name, type, version }; -} - /** * Creates an AASEndpoint form an URL. * @param url The current URL. * @returns An equivalent AASEndpoint. */ -export function urlToEndpoint(url: string): AASEndpoint { - const value = new URL(url); - const name = value.searchParams.get('name'); - const type = (value.searchParams.get('type') as AASEndpointType) ?? getEndpointType(value); - const version = value.searchParams.get('version') ?? '3.0'; +export function urlToEndpoint(url: string | URL): AASEndpoint { + const value = typeof url === 'string' ? new URL(url) : url; + const name = getEndpointName(value); + const type = getEndpointType(value); + const version = value.searchParams.get('version') ?? 'v3'; value.search = ''; value.hash = ''; - return { url: value.href, name: name ?? value.href, type, version }; -} - -/** - * Creates an URL that represents an AASEndpoint. - */ -export function endpointUrl(url: string, name: string, type: AASEndpointType, version?: string): URL { - const value = new URL(url); - value.searchParams.append('name', name); - value.searchParams.append('type', type); - if (version) { - value.searchParams.append('version', version); - } - - return value; + return { url: value.href.split('?')[0], name: name, type, version }; } diff --git a/projects/aas-server/src/app/controller/endpoints-controller.ts b/projects/aas-server/src/app/controller/endpoints-controller.ts index 1e3cc24e..5d95bdd8 100644 --- a/projects/aas-server/src/app/controller/endpoints-controller.ts +++ b/projects/aas-server/src/app/controller/endpoints-controller.ts @@ -54,7 +54,12 @@ export class EndpointsController extends AASController { @Security('bearerAuth', ['editor']) @OperationId('addEndpoint') public addEndpoint(@Path() name: string, @Body() endpoint: AASEndpoint): Promise { - return this.aasProvider.addEndpointAsync(name, endpoint); + try { + this.logger.start('addEndpoint'); + return this.aasProvider.addEndpointAsync(name, endpoint); + } finally { + this.logger.stop(); + } } /** @@ -65,7 +70,12 @@ export class EndpointsController extends AASController { @Security('bearerAuth', ['editor']) @OperationId('deleteEndpoint') public deleteEndpoint(@Path() name: string): Promise { - return this.aasProvider.removeEndpointAsync(name); + try { + this.logger.start('deleteEndpoint'); + return this.aasProvider.removeEndpointAsync(name); + } finally { + this.logger.stop(); + } } /** diff --git a/projects/aas-server/src/app/convert.ts b/projects/aas-server/src/app/convert.ts index fc78e440..9b167cb7 100644 --- a/projects/aas-server/src/app/convert.ts +++ b/projects/aas-server/src/app/convert.ts @@ -50,3 +50,12 @@ export function join(...args: string[]): string { return path; } + +export function slash(path: string): string { + const isExtendedLengthPath = path.startsWith('\\\\?\\'); + if (isExtendedLengthPath) { + return path; + } + + return path.replace(/\\/g, '/'); +} diff --git a/projects/aas-server/src/app/file-storage/file-storage-provider.ts b/projects/aas-server/src/app/file-storage/file-storage-provider.ts index c30ece23..17c63bce 100644 --- a/projects/aas-server/src/app/file-storage/file-storage-provider.ts +++ b/projects/aas-server/src/app/file-storage/file-storage-provider.ts @@ -37,6 +37,7 @@ export class FileStorageProvider { } private create(url: URL): FileStorage { + url = new URL(url); switch (url.protocol) { case 'file:': return new LocalFileStorage(this.variable.ASSETS); @@ -50,6 +51,8 @@ export class FileStorageProvider { url.password = this.variable.AAS_SERVER_PASSWORD; } + url.pathname = ''; + return new WebDAVStorage(url); default: throw new Error(`${url.href} is an invalid URL or a not supported file storage.`); diff --git a/projects/aas-server/src/app/file-storage/local-file-storage.ts b/projects/aas-server/src/app/file-storage/local-file-storage.ts index 58567a7c..d2641cec 100644 --- a/projects/aas-server/src/app/file-storage/local-file-storage.ts +++ b/projects/aas-server/src/app/file-storage/local-file-storage.ts @@ -7,7 +7,7 @@ *****************************************************************************/ import fs from 'fs'; -import { normalize, resolve, sep } from 'path'; +import { normalize, resolve, sep } from 'path/posix'; import { FileStorage, FileStorageEntry } from './file-storage.js'; export class LocalFileStorage extends FileStorage { diff --git a/projects/aas-server/src/app/file-storage/webdav-storage.ts b/projects/aas-server/src/app/file-storage/webdav-storage.ts index dc724ba0..d9a2dff1 100644 --- a/projects/aas-server/src/app/file-storage/webdav-storage.ts +++ b/projects/aas-server/src/app/file-storage/webdav-storage.ts @@ -23,12 +23,12 @@ export class WebDAVStorage extends FileStorage { this.url = url; } - public async mtime(path: string): Promise { + public override async mtime(path: string): Promise { const stat = (await this.client.stat(this.resolve(path))) as FileStat; return new Date(stat.lastmod); } - public exists(path: string): Promise { + public override exists(path: string): Promise { return this.client.exists(this.resolve(path)); } @@ -36,16 +36,16 @@ export class WebDAVStorage extends FileStorage { return this.client.createDirectory(this.resolve(path), { recursive }); } - public async writeFile(path: string, data: string | Buffer): Promise { + public override async writeFile(path: string, data: string | Buffer): Promise { await this.client.putFileContents(this.resolve(path), data, { overwrite: true }); } - public async readDir(path: string): Promise { + public override async readDir(path: string): Promise { const items = (await this.client.getDirectoryContents(this.resolve(path))) as FileStat[]; return items.map(item => ({ name: item.basename, path: item.filename, type: item.type })); } - public async readFile(path: string): Promise { + public override async readFile(path: string): Promise { const contents = await this.client.getFileContents(this.resolve(path)); if (typeof contents === 'string') { return Buffer.from(contents); @@ -54,7 +54,7 @@ export class WebDAVStorage extends FileStorage { return contents as Buffer; } - public async delete(path: string): Promise { + public override delete(path: string): Promise { return this.client.deleteFile(this.resolve(path)); } diff --git a/projects/aas-server/src/app/live/http/http-subscription.ts b/projects/aas-server/src/app/live/http/http-subscription.ts index 2830638f..15a9d14b 100644 --- a/projects/aas-server/src/app/live/http/http-subscription.ts +++ b/projects/aas-server/src/app/live/http/http-subscription.ts @@ -11,7 +11,7 @@ import { aas, changeType, LiveNode, LiveRequest } from 'common'; import { HttpSocketItem } from './http-socket-item.js'; import { Logger } from '../../logging/logger.js'; import { SocketClient } from '../socket-client.js'; -import { AasxServer } from '../../packages/aasx-server/aasx-server.js'; +import { AASServer } from '../../packages/aas-server/aas-server.js'; import { SocketSubscription } from '../socket-subscription.js'; export class HttpSubscription extends SocketSubscription { @@ -21,7 +21,7 @@ export class HttpSubscription extends SocketSubscription { public constructor( private readonly logger: Logger, - private readonly server: AasxServer, + private readonly server: AASServer, private readonly client: SocketClient, message: LiveRequest, env: aas.Environment, diff --git a/projects/aas-server/src/app/logging/logger-factory.ts b/projects/aas-server/src/app/logging/logger-factory.ts index 7540c774..21d869c9 100644 --- a/projects/aas-server/src/app/logging/logger-factory.ts +++ b/projects/aas-server/src/app/logging/logger-factory.ts @@ -6,7 +6,7 @@ * *****************************************************************************/ -import path from 'path'; +import path from 'path/posix'; import fs from 'fs'; import winston from 'winston'; import DailyRotateFile from 'winston-daily-rotate-file'; diff --git a/projects/aas-server/src/app/packages/aas-resource-factory.ts b/projects/aas-server/src/app/packages/aas-resource-factory.ts index b8b8095e..ed74f57e 100644 --- a/projects/aas-server/src/app/packages/aas-resource-factory.ts +++ b/projects/aas-server/src/app/packages/aas-resource-factory.ts @@ -10,9 +10,9 @@ import { inject, singleton } from 'tsyringe'; import { AASEndpoint, ApplicationError } from 'common'; import { Logger } from '../logging/logger.js'; import { AASResource } from './aas-resource.js'; -import { AasxDirectory } from './aasx-directory/aasx-directory.js'; -import { AasxServerV0 } from './aasx-server/aasx-server-v0.js'; -import { AasxServerV3 } from './aasx-server/aasx-server-v3.js'; +import { AasxDirectory } from './file-system/aasx-directory.js'; +import { AASServerV0 } from './aas-server/aas-server-v0.js'; +import { AASServerV3 } from './aas-server/aas-server-v3.js'; import { OpcuaServer } from './opcua/opcua-server.js'; import { ERRORS } from '../errors.js'; import { FileStorageProvider } from '../file-storage/file-storage-provider.js'; @@ -31,30 +31,27 @@ export class AASResourceFactory { * @returns A new instance of . */ public create(endpoint: AASEndpoint): AASResource { - let source: AASResource; - const url = new URL(endpoint.url); - switch (url.protocol) { - case 'http:': - case 'https': - if (endpoint.version === '3.0') { - source = new AasxServerV3(this.logger, endpoint.url, endpoint.name); - } else if (endpoint.version === '0.0') { - source = new AasxServerV0(this.logger, endpoint.url, endpoint.name); - } else { - throw new Error(`AASX server version ${endpoint.version} is not supported.`); + switch (endpoint.type) { + case 'AASServer': + switch (endpoint.version) { + case 'v3': + return new AASServerV3(this.logger, endpoint.url, endpoint.name); + case 'v0': + return new AASServerV0(this.logger, endpoint.url, endpoint.name); + default: + throw new Error(`AASX server version ${endpoint.version} is not supported.`); } - break; - case 'opc.tcp:': - source = new OpcuaServer(this.logger, endpoint.url, endpoint.name); - break; - case 'file:': - source = new AasxDirectory(this.logger, endpoint.url, endpoint.name, this.getFileStorage(url)); - break; + case 'OpcuaServer': + return new OpcuaServer(this.logger, endpoint.url, endpoint.name); + case 'WebDAV': + case 'FileSystem': { + const url = new URL(endpoint.url); + url.pathname = ''; + return new AasxDirectory(this.logger, endpoint.url, endpoint.name, this.getFileStorage(url)); + } default: throw new Error('Not implemented.'); } - - return source; } /** @@ -64,30 +61,34 @@ export class AASResourceFactory { */ public async testAsync(endpoint: AASEndpoint): Promise { try { - const url = new URL(endpoint.url); - switch (url.protocol) { - case 'http:': - case 'https:': { - const version = endpoint.version ?? '3.0'; - if (version === '3.0') { - await new AasxServerV3(this.logger, endpoint.url, endpoint.name).testAsync(); - } else if (version === '0.0') { - await new AasxServerV0(this.logger, endpoint.url, endpoint.name).testAsync(); - } else { - throw new Error('Not implemented.'); + switch (endpoint.type) { + case 'AASServer': + switch (endpoint.version) { + case 'v3': + await new AASServerV3(this.logger, endpoint.url, endpoint.name).testAsync(); + break; + case 'v0': + await new AASServerV0(this.logger, endpoint.url, endpoint.name).testAsync(); + break; + default: + throw new Error(`AASX server version ${endpoint.version} is not supported.`); } break; - } - case 'opc.tcp:': + case 'OpcuaServer': await new OpcuaServer(this.logger, endpoint.url, endpoint.name).testAsync(); break; - case 'file:': - await new AasxDirectory( - this.logger, - endpoint.url, - endpoint.name, - this.getFileStorage(url), - ).testAsync(); + case 'WebDAV': + case 'FileSystem': + { + const url = new URL(endpoint.url); + url.pathname = ''; + await new AasxDirectory( + this.logger, + endpoint.url, + endpoint.name, + this.getFileStorage(url), + ).testAsync(); + } break; default: throw new Error('Not implemented.'); @@ -101,7 +102,7 @@ export class AASResourceFactory { } } - private getFileStorage(url: URL): FileStorage { + private getFileStorage(url: string | URL): FileStorage { return this.fileStorageProvider.get(url); } } diff --git a/projects/aas-server/src/app/packages/aasx-server/aasx-server-package.ts b/projects/aas-server/src/app/packages/aas-server/aas-server-package.ts similarity index 94% rename from projects/aas-server/src/app/packages/aasx-server/aasx-server-package.ts rename to projects/aas-server/src/app/packages/aas-server/aas-server-package.ts index 315f4f9f..5c686537 100644 --- a/projects/aas-server/src/app/packages/aasx-server/aasx-server-package.ts +++ b/projects/aas-server/src/app/packages/aas-server/aas-server-package.ts @@ -10,11 +10,11 @@ import { AASDocument, diffAsync, aas } from 'common'; import { AASPackage } from '../aas-package.js'; import { AASResource } from '../aas-resource.js'; import { Logger } from '../../logging/logger.js'; -import { AasxServer } from './aasx-server.js'; +import { AASServer } from './aas-server.js'; import { ImageProcessing } from '../../image-processing.js'; -export class AasxServerPackage extends AASPackage { - private readonly server: AasxServer; +export class AASServerPackage extends AASPackage { + private readonly server: AASServer; private readonly idShort: string; /** @@ -26,7 +26,7 @@ export class AasxServerPackage extends AASPackage { public constructor(logger: Logger, resource: AASResource, idShort: string) { super(logger); - this.server = resource as AasxServer; + this.server = resource as AASServer; this.idShort = idShort; } diff --git a/projects/aas-server/src/app/packages/aasx-server/aasx-server-v0.ts b/projects/aas-server/src/app/packages/aas-server/aas-server-v0.ts similarity index 98% rename from projects/aas-server/src/app/packages/aasx-server/aasx-server-v0.ts rename to projects/aas-server/src/app/packages/aas-server/aas-server-v0.ts index f69b47c9..8ecd6b57 100644 --- a/projects/aas-server/src/app/packages/aasx-server/aasx-server-v0.ts +++ b/projects/aas-server/src/app/packages/aas-server/aas-server-v0.ts @@ -9,7 +9,7 @@ import { aas, DifferenceItem, selectSubmodel } from 'common'; import { Logger } from '../../logging/logger.js'; import { JsonReaderV2 } from '../json-reader-v2.js'; -import { AasxServer } from './aasx-server.js'; +import { AASServer } from './aas-server.js'; import { JsonWriterV2 } from '../json-writer-v2.js'; import * as aasV2 from '../../types/aas-v2.js'; @@ -17,7 +17,7 @@ interface AASList { aaslist: string[]; } -export class AasxServerV0 extends AasxServer { +export class AASServerV0 extends AASServer { public constructor(logger: Logger, url: string, name: string) { super(logger, url, name); } diff --git a/projects/aas-server/src/app/packages/aasx-server/aasx-server-v3.ts b/projects/aas-server/src/app/packages/aas-server/aas-server-v3.ts similarity index 98% rename from projects/aas-server/src/app/packages/aasx-server/aasx-server-v3.ts rename to projects/aas-server/src/app/packages/aas-server/aas-server-v3.ts index 44035816..0815b564 100644 --- a/projects/aas-server/src/app/packages/aasx-server/aasx-server-v3.ts +++ b/projects/aas-server/src/app/packages/aas-server/aas-server-v3.ts @@ -10,7 +10,7 @@ import FormData from 'form-data'; import { cloneDeep } from 'lodash-es'; import { createReadStream } from 'fs'; import { encodeBase64Url } from '../../convert.js'; -import { AasxServer } from './aasx-server.js'; +import { AASServer } from './aas-server.js'; import { Logger } from '../../logging/logger.js'; import { JsonReader } from '../json-reader.js'; import { JsonWriter } from '../json-writer.js'; @@ -62,12 +62,12 @@ interface PagedResult { paging_metadata: PagedResultPagingMetadata; } -export class AasxServerV3 extends AasxServer { +export class AASServerV3 extends AASServer { public constructor(logger: Logger, url: string, name: string) { super(logger, url, name); } - public override readonly version = '3.0'; + public override readonly version = 'v3'; public readonly readOnly = false; diff --git a/projects/aas-server/src/app/packages/aasx-server/aasx-server.ts b/projects/aas-server/src/app/packages/aas-server/aas-server.ts similarity index 95% rename from projects/aas-server/src/app/packages/aasx-server/aasx-server.ts rename to projects/aas-server/src/app/packages/aas-server/aas-server.ts index 8090dee9..213f37c4 100644 --- a/projects/aas-server/src/app/packages/aasx-server/aasx-server.ts +++ b/projects/aas-server/src/app/packages/aas-server/aas-server.ts @@ -13,7 +13,7 @@ import { HttpSubscription } from '../../live/http/http-subscription.js'; import { SocketClient } from '../../live/socket-client.js'; import { AASPackage } from '../aas-package.js'; import { AASResource } from '../aas-resource.js'; -import { AasxServerPackage } from './aasx-server-package.js'; +import { AASServerPackage } from './aas-server-package.js'; import { SocketSubscription } from '../../live/socket-subscription.js'; interface PropertyValue { @@ -21,7 +21,7 @@ interface PropertyValue { } /** Provides access to an AASX-Server. */ -export abstract class AasxServer extends AASResource { +export abstract class AASServer extends AASResource { private reentry = 0; /** @@ -78,7 +78,7 @@ export abstract class AasxServer extends AASResource { } public createPackage(address: string): AASPackage { - return new AasxServerPackage(this.logger, this, address); + return new AASServerPackage(this.logger, this, address); } public override createSubscription( diff --git a/projects/aas-server/src/app/packages/aasx-directory/aasx-directory.ts b/projects/aas-server/src/app/packages/file-system/aasx-directory.ts similarity index 99% rename from projects/aas-server/src/app/packages/aasx-directory/aasx-directory.ts rename to projects/aas-server/src/app/packages/file-system/aasx-directory.ts index 8e347c79..56b2d3b3 100644 --- a/projects/aas-server/src/app/packages/aasx-directory/aasx-directory.ts +++ b/projects/aas-server/src/app/packages/file-system/aasx-directory.ts @@ -7,7 +7,7 @@ *****************************************************************************/ import { aas, ApplicationError } from 'common'; -import { join } from 'path'; +import { join } from 'path/posix'; import { readFile } from 'fs/promises'; import { ERRORS } from '../../errors.js'; import { FileStorage } from '../../file-storage/file-storage.js'; diff --git a/projects/aas-server/src/app/packages/aasx-directory/aasx-package.ts b/projects/aas-server/src/app/packages/file-system/aasx-package.ts similarity index 88% rename from projects/aas-server/src/app/packages/aasx-directory/aasx-package.ts rename to projects/aas-server/src/app/packages/file-system/aasx-package.ts index d12424e4..df413eb1 100644 --- a/projects/aas-server/src/app/packages/aasx-directory/aasx-package.ts +++ b/projects/aas-server/src/app/packages/file-system/aasx-package.ts @@ -6,7 +6,7 @@ * *****************************************************************************/ -import { basename, extname } from 'path'; +import { basename, extname } from 'path/posix'; import jszip from 'jszip'; import xpath from 'xpath'; import { DOMParser } from '@xmldom/xmldom'; @@ -17,12 +17,15 @@ import { Lazy } from '../../lazy.js'; import { AASPackage } from '../aas-package.js'; import { AASResource } from '../aas-resource.js'; import { Logger } from '../../logging/logger.js'; -import { XmlReader } from '../xml-reader.js'; +import { XmlReaderV1 } from '../xml-reader-v1.js'; import { AASReader } from '../aas-reader.js'; import { JsonReaderV2 } from '../json-reader-v2.js'; import { JsonReader } from '../json-reader.js'; import * as aasV2 from '../../types/aas-v2.js'; import { ImageProcessing } from '../../image-processing.js'; +import { HTMLDocumentElement } from '../../types/html-document-element.js'; +import { XmlReaderV2 } from '../xml-reader_v2.js'; +import { XmlReader } from '../xml-reader.js'; export class AasxPackage extends AASPackage { private readonly file: string; @@ -121,7 +124,7 @@ export class AasxPackage extends AASPackage { switch (extension) { case '.xml': { const xml = await this.getZipEntryAsync(name); - return new XmlReader(this.logger, xml); + return this.createXmlReader(xml); } case '.json': { const env = JSON.parse(await this.getZipEntryAsync(name)); @@ -218,6 +221,27 @@ export class AasxPackage extends AASPackage { throw new Error('Not implemented.'); } + private createXmlReader(xml: string): AASReader { + const document = new DOMParser().parseFromString(xml); + const nsMap = (document.documentElement as HTMLDocumentElement)._nsMap ?? {}; + for (const prefix in nsMap) { + const uri = nsMap[prefix]; + if (uri === 'http://www.admin-shell.io/aas/1/0') { + return new XmlReaderV1(this.logger, document); + } + + if (uri === 'http://www.admin-shell.io/aas/2/0') { + return new XmlReaderV2(this.logger, document); + } + + if (uri === 'https://admin-shell.io/aas/3/0') { + return new XmlReader(this.logger, document); + } + } + + throw new Error('Invalid operation.'); + } + private async createThumbnail(): Promise { try { const input = await this.getThumbnailAsync(); diff --git a/projects/aas-server/src/app/packages/json-reader.ts b/projects/aas-server/src/app/packages/json-reader.ts index a7a8d541..6fceca97 100644 --- a/projects/aas-server/src/app/packages/json-reader.ts +++ b/projects/aas-server/src/app/packages/json-reader.ts @@ -506,15 +506,14 @@ export class JsonReader extends AASReader { } private readReferenceElement(source: aas.ReferenceElement, ancestors?: aas.Referable[]): aas.ReferenceElement { - if (!source.value) { - throw new Error('ReferenceElement.value'); - } - const reference: aas.ReferenceElement = { ...this.readSubmodelElementType(source, ancestors), - value: this.readReference(source.value), }; + if (source.value) { + reference.value = this.readReference(source.value); + } + return reference; } diff --git a/projects/aas-server/src/app/packages/json-writer.ts b/projects/aas-server/src/app/packages/json-writer.ts index f9b6f400..3f97add0 100644 --- a/projects/aas-server/src/app/packages/json-writer.ts +++ b/projects/aas-server/src/app/packages/json-writer.ts @@ -6,7 +6,7 @@ * *****************************************************************************/ -import path from 'path'; +import path from 'path/posix'; import { aas, extensionToMimeType } from 'common'; import { AASWriter } from './aas-writer.js'; diff --git a/projects/aas-server/src/app/packages/xml-reader-v1.ts b/projects/aas-server/src/app/packages/xml-reader-v1.ts new file mode 100644 index 00000000..b6cc2e42 --- /dev/null +++ b/projects/aas-server/src/app/packages/xml-reader-v1.ts @@ -0,0 +1,718 @@ +/****************************************************************************** + * + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, + * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft + * zur Foerderung der angewandten Forschung e.V. + * + *****************************************************************************/ + +import { aas, determineType } from 'common'; +import { useNamespaces, XPathSelect } from 'xpath'; +import { DOMParser } from '@xmldom/xmldom'; +import { Logger } from '../logging/logger.js'; +import { AASReader } from './aas-reader.js'; +import { HTMLDocumentElement } from '../types/html-document-element.js'; + +export class XmlReaderV1 extends AASReader { + private readonly select: XPathSelect; + private readonly document: Document; + private iec61360 = 'IEC61360'; + + public constructor( + private readonly logger: Logger, + xmlSource: string | Document, + ) { + super(); + + this.document = typeof xmlSource === 'string' ? new DOMParser().parseFromString(xmlSource) : xmlSource; + this.select = useNamespaces(this.getNamespaces()); + } + + public readEnvironment(): aas.Environment { + const conceptDescriptions = this.readConceptDescriptions(); + const assetAdministrationShells = this.readAssetAdministrationShells(); + const submodels = this.readSubmodels(); + return { assetAdministrationShells, submodels, conceptDescriptions }; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public read(data: unknown): aas.Referable { + throw new Error('Not implemented.'); + } + + private getNamespaces(): { [key: string]: string } { + const nsMap = (this.document.documentElement as HTMLDocumentElement)._nsMap ?? {}; + const namespaces: { [key: string]: string } = {}; + for (const prefix in nsMap) { + const uri = nsMap[prefix]; + if (uri.startsWith('http://www.admin-shell.io/IEC61360/')) { + this.iec61360 = prefix; + namespaces[prefix] = uri; + } else if (uri.startsWith('http://www.admin-shell.io/aas/')) { + namespaces['aas'] = uri; + } + } + + return namespaces; + } + + private readAssetInformation(): aas.AssetInformation { + let assetKind: aas.AssetKind | undefined; + let globalAssetId: string | undefined; + const node = this.selectNode('/aas:aasenv/aas:assets/aas:asset', this.document); + if (node) { + assetKind = this.selectNode('./aas:kind', node)?.textContent as aas.AssetKind; + globalAssetId = this.readIdentifier(node); + } else { + assetKind = 'Instance'; + } + + return { assetKind, globalAssetId }; + } + + private readAssetAdministrationShells(): aas.AssetAdministrationShell[] { + const shells: aas.AssetAdministrationShell[] = []; + for (const node of this.selectNodes( + '/aas:aasenv/aas:assetAdministrationShells/aas:assetAdministrationShell', + this.document, + )) { + const shell = this.readAssetAdministrationShell(node); + shells.push(shell); + } + + return shells; + } + + private readAssetAdministrationShell(node: Node): aas.AssetAdministrationShell { + const assetInformation = this.readAssetInformation(); + if (!assetInformation) { + throw new Error('AssetAdministrationShell.asset'); + } + + const shell: aas.AssetAdministrationShell = { + ...this.readIdentifiable(node), + ...this.readHasDataSpecification(node), + assetInformation, + }; + + const submodels = this.readReferences('./aas:submodelRefs/aas:submodelRef', node); + if (submodels.length > 0) { + shell.submodels = submodels; + } + + const administration = this.readAdministrationInformation('./aas:administration', node); + if (administration) { + shell.administration = administration; + } + + return shell; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private readAdministrationInformation(path: string, node: Node): aas.AdministrativeInformation | undefined { + return undefined; + } + + private readSubmodels(): aas.Submodel[] { + const submodels: aas.Submodel[] = []; + for (const node of this.selectNodes('/aas:aasenv/aas:submodels/aas:submodel', this.document)) { + const submodel = this.readSubmodel(node); + submodels.push(submodel); + } + + return submodels; + } + + private readSubmodel(node: Node): aas.Submodel { + const submodel: aas.Submodel = { + ...this.readIdentifiable(node), + ...this.readHaSemantic(node), + ...this.readQualifiable(node), + ...this.readHasKind(node), + ...this.readHasDataSpecification(node), + }; + + const submodelElements = this.readSubmodelElements(node, { + type: 'ModelReference', + keys: [ + { + type: 'Submodel', + value: submodel.id, + }, + ], + }); + if (submodelElements.length > 0) { + submodel.submodelElements = submodelElements; + } + + return submodel; + } + + private readSubmodelElements(node: Node, parent?: aas.Reference): aas.SubmodelElement[] { + const submodelElements: aas.SubmodelElement[] = []; + for (const child of this.selectNodes('./aas:submodelElements/aas:submodelElement/*[1]', node)) { + const submodelElement = this.readSubmodelElement(child, parent); + if (submodelElement) { + submodelElements.push(submodelElement); + } + } + + return submodelElements; + } + + private readCollectionValue(node: Node, parent?: aas.Reference): aas.SubmodelElement[] { + const submodelElements: aas.SubmodelElement[] = []; + for (const child of this.selectNodes('./aas:value/aas:submodelElement/*[1]', node)) { + const submodelElement = this.readSubmodelElement(child, parent); + if (submodelElement) { + submodelElements.push(submodelElement); + } + } + + return submodelElements; + } + + private readSubmodelElement(node: Node, parent?: aas.Reference): aas.SubmodelElement | undefined { + let submodelElement: aas.SubmodelElement | undefined; + const modelType = this.getModelTypeFromLocalName(node); + switch (modelType) { + case 'AnnotatedRelationshipElement': + submodelElement = this.readAnnotatedRelationshipElement(node, parent); + break; + case 'BasicEventElement': + submodelElement = this.readBasicEventElement(node, parent); + break; + case 'Blob': + submodelElement = this.readBlob(node, parent); + break; + case 'File': + submodelElement = this.readFile(node, parent); + break; + case 'MultiLanguageProperty': + submodelElement = this.readMultiLanguageProperty(node, parent); + break; + case 'Property': + submodelElement = this.readProperty(node, parent); + break; + case 'Range': + submodelElement = this.readRange(node, parent); + break; + case 'ReferenceElement': + submodelElement = this.readReferenceElement(node, parent); + break; + case 'RelationshipElement': + submodelElement = this.readRelationshipElement(node, parent); + break; + case 'SubmodelElementCollection': + submodelElement = this.readSubmodelElementCollection(node, parent); + break; + default: + break; + } + + return submodelElement; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private readAnnotatedRelationshipElement(node: Node, parent?: aas.Reference): aas.AnnotatedRelationshipElement { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private readBasicEventElement(node: Node, parent?: aas.Reference): aas.BasicEventElement { + throw new Error('Method not implemented.'); + } + + private readBlob(node: Node, parent?: aas.Reference): aas.Blob { + const contentType = this.selectNode('./aas:mimeType', node)?.textContent; + if (!contentType) { + throw new Error('File.mimetype'); + } + + const blob: aas.Blob = { + ...this.readSubmodelElementType(node, parent), + contentType, + }; + + const value = this.selectNode('./aas:value', node)?.textContent; + if (value) { + blob.value = value; + } + + return blob; + } + + private readSubmodelElementCollection(node: Node, parent?: aas.Reference): aas.SubmodelElementCollection { + const base = this.readSubmodelElementType(node, parent); + const collection: aas.SubmodelElementCollection = { + ...base, + value: this.readCollectionValue(node, parent ? this.createReference(parent, base) : undefined), + }; + + return collection; + } + + private readProperty(node: Node, parent?: aas.Reference): aas.Property { + const valueNode = this.selectNode('./aas:value', node); + let value = valueNode?.textContent; + + const valueTypeNode = this.selectNode('./aas:valueType', node); + let valueType: aas.DataTypeDefXsd | undefined; + if (valueTypeNode?.textContent) { + valueType = this.toDataTypeDefXsd(valueTypeNode.textContent); + } + + if (!valueType && value != null) { + valueType = determineType(value); + } + + if (!valueType) { + valueType = 'xs:string'; + value = '!!! Undefined value type !!!'; + } + + const property: aas.Property = { ...this.readSubmodelElementType(node, parent), valueType }; + if (value) { + property.value = value; + } + + return property; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private readRange(node: Node, parent?: aas.Reference): aas.Range { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private readRelationshipElement(node: Node, parent?: aas.Reference): aas.RelationshipElement { + throw new Error('Method not implemented.'); + } + + private readFile(node: Node, parent?: aas.Reference): aas.File { + let contentType = this.selectNode('./aas:mimeType', node)?.textContent; + if (!contentType) { + contentType = ''; + } + + const file: aas.File = { + ...this.readSubmodelElementType(node, parent), + contentType, + }; + + const value = this.selectNode('./aas:value', node)?.textContent; + if (value) { + file.value = value; + } + + return file; + } + + private readMultiLanguageProperty(node: Node, parent?: aas.Reference): aas.MultiLanguageProperty { + const langString = this.readLangString('./aas:value', node) ?? []; + return { ...this.readSubmodelElementType(node, parent), value: langString }; + } + + private readReferenceElement(node: Node, parent?: aas.Reference): aas.ReferenceElement { + const value = this.getReference(node); + return { ...this.readSubmodelElementType(node, parent), value }; + } + + private readSubmodelElementType(node: Node, parent?: aas.Reference): aas.SubmodelElement { + return { + ...this.readReferable(node, undefined, parent), + ...this.readHaSemantic(node), + ...this.readHasKind(node), + ...this.readHasDataSpecification(node), + ...this.readQualifiable(node), + }; + } + + private readIdentifiable(node: Node): aas.Identifiable { + const id = this.readIdentifier(node); + const identifiable: aas.Identifiable = { ...this.readReferable(node, id), id }; + const administration = this.readAdministrativeInformation(node); + if (administration) { + identifiable.administration = administration; + } + + return identifiable; + } + + private readReferable(node: Node, id?: string, parent?: aas.Reference): aas.Referable { + let idShort = this.selectNode('./aas:idShort', node)?.textContent; + if (!idShort) { + idShort = id ? this.createIdShort(id) : ''; + } + + const referable: aas.Referable = { + idShort, + modelType: this.getModelTypeFromLocalName(node), + }; + + if (parent) { + referable.parent = parent; + } + + const category = this.selectNode('./aas:category', node)?.textContent as aas.Category | undefined; + if (category) { + referable.category = category; + } + + return referable; + } + + private readHaSemantic(node: Node): aas.HasSemantic { + const semanticId = this.readReference('./aas:semanticId', node); + return semanticId ? { semanticId } : {}; + } + + private readHasKind(node: Node): aas.HasKind { + let kind = this.selectNode('aas:kind', node)?.textContent as aas.ModellingKind; + if (!kind) { + kind = 'Instance'; + } + + return { kind }; + } + + private readHasDataSpecification(node: Node): aas.HasDataSpecification { + const embeddedDataSpecifications: aas.EmbeddedDataSpecification[] = []; + for (const child of this.selectNodes('./aas:embeddedDataSpecification', node)) { + const dataSpecification = + this.readReference('./aas:hasDataSpecification', child) ?? + this.readReference('./aas:dataSpecification', child); + + if (!dataSpecification) { + throw new Error('EmbeddedDataSpecification.dataSpecification'); + } + + const dataSpecificationContent = this.readDataSpecificationContent(child); + if (dataSpecificationContent) { + embeddedDataSpecifications.push({ dataSpecification, dataSpecificationContent }); + } + } + + return embeddedDataSpecifications.length > 0 ? { embeddedDataSpecifications } : {}; + } + + private readDataSpecificationContent(parent: Node): aas.DataSpecificationContent | undefined { + const node = this.selectNode('./aas:dataSpecificationContent/aas:dataSpecificationIEC61360', parent); + if (node) { + return this.readDataSpecificationIEC61360(node); + } + + return undefined; + } + + private readDataSpecificationIEC61360(node: Node): aas.DataSpecificationIEC61360 { + const preferredName = this.readLangString(`./${this.iec61360}:preferredName`, node); + if (!preferredName) { + throw new Error('DataSpecificationIEC61360Content.preferredName'); + } + + const dataSpecification: aas.DataSpecificationIEC61360 = { + modelType: this.getModelTypeFromLocalName(node), + preferredName, + }; + + const shortName = this.readLangString(`./${this.iec61360}:shortName`, node); + if (shortName) { + dataSpecification.shortName = shortName; + } + + const unit = this.selectNode(`./${this.iec61360}:unit`, node)?.textContent; + if (unit) { + dataSpecification.unit = unit; + } + + const unitId = this.readReference(`./${this.iec61360}:unitId`, node); + if (unit) { + dataSpecification.unitId = unitId; + } + + const sourceOfDefinition = this.selectNode(`./${this.iec61360}:sourceOfDefinition`, node)?.textContent; + if (sourceOfDefinition) { + dataSpecification.sourceOfDefinition = sourceOfDefinition; + } + + const symbol = this.selectNode(`./${this.iec61360}:symbol`, node)?.textContent; + if (symbol) { + dataSpecification.symbol = symbol; + } + + const dataType = this.selectNode(`./${this.iec61360}:dataType`, node)?.textContent as aas.DataTypeIEC61360; + if (dataType) { + dataSpecification.dataType = dataType; + } + + const definition = this.readLangString(`./${this.iec61360}:definition`, node); + if (definition) { + dataSpecification.definition = definition; + } + + const valueFormat = this.selectNode(`./${this.iec61360}:valueFormat`, node)?.textContent; + if (valueFormat) { + dataSpecification.valueFormat = valueFormat; + } + + const valueList = this.readValueList(`./${this.iec61360}:valueList`, node); + if (valueList) { + dataSpecification.valueList = valueList; + } + + const value = this.selectNode(`./${this.iec61360}:value`, node)?.textContent; + if (value) { + dataSpecification.value = value; + } + + const levelType = this.selectNode(`./${this.iec61360}:levelType`, node)?.textContent as aas.LevelType; + if (levelType) { + dataSpecification.levelType = levelType; + } + + return dataSpecification; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private readValueList(path: string, node: Node): aas.ValueList | undefined { + return undefined; + } + + private readQualifiable(node: Node): aas.Qualifiable { + const qualifiable: aas.Qualifiable = {}; + const qualifiers = this.readQualifiers('./aas:qualifier', node); + if (qualifiers) { + qualifiable.qualifiers = qualifiers; + } + + return qualifiable; + } + + private readQualifiers(path: string, parent: Node): aas.Qualifier[] | undefined { + let qualifiers: aas.Qualifier[] | undefined; + const node = this.selectNode(path, parent); + if (node) { + qualifiers = []; // ToDo. + } + + return qualifiers; + } + + private readReference(path: string, parent: Node): aas.Reference | undefined { + let reference: aas.Reference | undefined; + const node = this.selectNode(path, parent); + if (node) { + reference = { type: 'ModelReference', keys: [] }; + for (const keyNode of this.selectNodes('./aas:keys/aas:key', node)) { + reference.keys.push(this.getKey(keyNode)); + } + } + + return reference; + } + + private readReferences(path: string, parent: Node): aas.Reference[] { + const references: aas.Reference[] = []; + for (const node of this.selectNodes(path, parent)) { + const reference: aas.Reference = { type: 'ModelReference', keys: [] }; + for (const keyNode of this.selectNodes('./aas:keys/aas:key', node)) { + reference.keys.push(this.getKey(keyNode)); + } + + references.push(reference); + } + + return references; + } + + private readAdministrativeInformation(node: Node): aas.AdministrativeInformation | undefined { + let value: aas.AdministrativeInformation | undefined; + const version = this.selectNode('./aas:administration/aas:version', node)?.textContent; + const revision = this.selectNode('./aas:administration/aas:revision', node)?.textContent; + if (version || revision) { + value = {}; + if (version) { + value.version = version; + } + + if (revision) { + value.revision = revision; + } + } + + return value; + } + + private readLangString(path: string, parent: Node): aas.LangString[] | undefined { + let langString: aas.LangString[] | undefined; + const content = this.selectNode(path, parent); + if (content) { + langString = []; + for (const node of this.selectNodes('./aas:langString', content)) { + const language = (node as Element).getAttribute('lang')!.toLowerCase(); + const text = node.textContent ?? ''; + langString.push({ language, text }); + } + + if (langString.length === 0 && content.textContent) { + langString.push({ language: 'en', text: content.textContent }); + } + } + + return langString; + } + + private readConceptDescriptions(): aas.ConceptDescription[] { + const conceptDescriptions: aas.ConceptDescription[] = []; + for (const node of this.selectNodes( + '/aas:aasenv/aas:conceptDescriptions/aas:conceptDescription', + this.document, + )) { + conceptDescriptions.push(this.readConceptDescription(node)); + } + + return conceptDescriptions; + } + + private readConceptDescription(node: Node): aas.ConceptDescription { + const conceptDescription: aas.ConceptDescription = { + ...this.readIdentifiable(node), + ...this.readHasDataSpecification(node), + }; + + const isCaseOf: aas.Reference[] = []; + for (const refNode of this.selectNodes('./aas:isCaseOf', node)) { + isCaseOf.push(this.readReference('.', refNode)!); + } + + if (isCaseOf.length > 0) { + conceptDescription.isCaseOf = isCaseOf; + } + + return conceptDescription; + } + + private getModelTypeFromLocalName(node: Node): aas.ModelType { + const localName: string = (node as Element).localName; + return (localName.charAt(0).toUpperCase() + localName.substring(1)) as aas.ModelType; + } + + private selectNode(query: string, node: Node): Node | undefined { + return this.select(query, node, true) as Node | undefined; + } + + private getNode(query: string, node: Node): Node { + const result = this.select(query, node, true); + if (!result) { + throw new Error(`Query '${query}' returns no result.`); + } + + return result as Node; + } + + private selectNodes(query: string, node: Node): Node[] { + return this.select(query, node) as Node[]; + } + + private getReference(node: Node): aas.Reference { + return { + type: 'ModelReference', + keys: this.selectNodes('./aas:value/aas:keys/aas:key', node).map(item => this.getKey(item)), + }; + } + + private getKey(node: Node): aas.Key { + const element = node as Element; + return { + type: element.getAttribute('type') as aas.KeyTypes, + value: element.textContent ?? '', + }; + } + + private readIdentifier(node: Node): string { + const id = this.selectNode('./aas:identification', node)?.textContent; + if (id == null) { + throw new Error('./aas:identification'); + } + + return id; + } + + private toDataTypeDefXsd(source: string): aas.DataTypeDefXsd { + switch (source) { + case 'anyURI': + return 'xs:anyURI'; + case 'base64Binary': + return 'xs:base64Binary'; + case 'boolean': + return 'xs:boolean'; + case 'byte': + return 'xs:byte'; + case 'Date': + case 'date': + return 'xs:date'; + case 'dateTime': + return 'xs:dateTime'; + case 'dateTimeStamp': + return 'xs:dateTime'; + case 'dayTimeDuration': + return 'xs:duration'; + case 'Decimal': + case 'decimal': + return 'xs:decimal'; + case 'double': + return 'xs:double'; + case 'duration': + return 'xs:duration'; + case 'float': + return 'xs:float'; + case 'gDay': + return 'xs:gDay'; + case 'gMonth': + return 'xs:gMonth'; + case 'gMonthDay': + return 'xs:gMonthDay'; + case 'gYear': + return 'xs:gYear'; + case 'gYearMonth': + return 'xs:gYearMonth'; + case 'hexBinary': + return 'xs:hexBinary'; + case 'int': + return 'xs:int'; + case 'integer': + return 'xs:integer'; + case 'long': + return 'xs:long'; + case 'negativeInteger': + return 'xs:negativeInteger'; + case 'nonNegativeInteger': + return 'xs:nonNegativeInteger'; + case 'nonPositiveInteger': + return 'xs:nonPositiveInteger'; + case 'positiveInteger': + return 'xs:positiveInteger'; + case 'short': + return 'xs:short'; + case 'langString': + case 'String': + case 'string': + return 'xs:string'; + case 'time': + return 'xs:time'; + case 'unsignedByte': + return 'xs:unsignedByte'; + case 'unsignedInt': + return 'xs:unsignedInt'; + case 'unsignedLong': + return 'xs:unsignedLong'; + case 'unsignedShort': + return 'xs:unsignedShort'; + case 'yearMonthDuration': + return 'xs:duration'; + default: + throw new Error(`${source} is an unknown value type.`); + } + } +} diff --git a/projects/aas-server/src/app/packages/xml-reader.ts b/projects/aas-server/src/app/packages/xml-reader.ts index db9e1918..83698dcb 100644 --- a/projects/aas-server/src/app/packages/xml-reader.ts +++ b/projects/aas-server/src/app/packages/xml-reader.ts @@ -11,23 +11,19 @@ import { useNamespaces, XPathSelect } from 'xpath'; import { DOMParser } from '@xmldom/xmldom'; import { Logger } from '../logging/logger.js'; import { AASReader } from './aas-reader.js'; - -interface NamespaceMap { - _nsMap: { [key: string]: string }; -} +import { HTMLDocumentElement } from '../types/html-document-element.js'; export class XmlReader extends AASReader { private readonly select: XPathSelect; private readonly document: Document; - private iec61360 = 'IEC61360'; public constructor( private readonly logger: Logger, - xmlSource: string, + xmlSource: string | Document, ) { super(); - this.document = new DOMParser().parseFromString(xmlSource); + this.document = typeof xmlSource === 'string' ? new DOMParser().parseFromString(xmlSource) : xmlSource; this.select = useNamespaces(this.getNamespaces()); } @@ -44,20 +40,11 @@ export class XmlReader extends AASReader { } private getNamespaces(): { [key: string]: string } { - let nsMap: { [key: string]: string }; - if ('_nsMap' in this.document.documentElement) { - nsMap = (this.document.documentElement as unknown as NamespaceMap)._nsMap; - } else { - nsMap = {}; - } - + const nsMap = (this.document.documentElement as HTMLDocumentElement)._nsMap ?? {}; const namespaces: { [key: string]: string } = {}; for (const prefix in nsMap) { const uri = nsMap[prefix]; - if (uri.startsWith('http://www.admin-shell.io/IEC61360/')) { - this.iec61360 = prefix; - namespaces[prefix] = uri; - } else if (uri.startsWith('http://www.admin-shell.io/aas/')) { + if (uri === 'https://admin-shell.io/aas/3/0') { namespaces['aas'] = uri; } } @@ -65,24 +52,96 @@ export class XmlReader extends AASReader { return namespaces; } - private readAssetInformation(): aas.AssetInformation { - let assetKind: aas.AssetKind | undefined; - let globalAssetId: string | undefined; - const node = this.selectNode('/aas:aasenv/aas:assets/aas:asset', this.document); - if (node) { - assetKind = this.selectNode('./aas:kind', node)?.textContent as aas.AssetKind; - globalAssetId = this.readIdentifier(node); - } else { - assetKind = 'Instance'; + private readAssetInformation(node: Node): aas.AssetInformation { + const asset = this.selectNode('/aas:assetInformation', node); + if (!asset) { + return { assetKind: 'Instance' }; + } + + const value: aas.AssetInformation = { + assetKind: this.getTextContent('./aas:kind', asset) as aas.AssetKind, + }; + + const globalAssetId = this.selectTextContent('./aas:identification', asset); + if (globalAssetId) { + value.globalAssetId = globalAssetId; + } + + const assetType = this.selectTextContent('./aas:assetKind', asset); + if (assetType) { + value.assetType = assetType; + } + + const defaultThumbnail = this.readResource(this.selectNode('./aas:defaultThumbnail', asset)); + if (defaultThumbnail) { + value.defaultThumbnail = defaultThumbnail; + } + + const specificAssetIds = this.readSpecificAssetIds(this.selectNode('./aas:specificAssetIds', asset)); + if (specificAssetIds) { + value.specificAssetIds = specificAssetIds; + } + + return value; + } + + private readResource(node: Node | undefined): aas.Resource | undefined { + if (!node) { + return undefined; + } + + const value: aas.Resource = { + path: this.getTextContent('./aas:path', node), + }; + + const contentType = this.selectTextContent('./aas:contentType', node); + if (contentType) { + value.contentType = contentType; + } + + return value; + } + + private readSpecificAssetIds(node: Node | undefined): aas.SpecificAssetId[] | undefined { + if (!node) { + return undefined; } - return { assetKind, globalAssetId }; + const values: aas.SpecificAssetId[] = []; + for (const child of this.selectNodes('./aas:specificAssetId', node)) { + const value = this.readSpecificAssetId(child); + if (value) { + values.push(value); + } + } + + return values; + } + + private readSpecificAssetId(node: Node | undefined): aas.SpecificAssetId | undefined { + if (!node) { + return undefined; + } + + const externalSubjectId = this.readReference(this.selectNode('./aas:externalSubjectId', node)); + if (!externalSubjectId) { + throw new Error('SpecificAssetId.externalSubjectId'); + } + + const value: aas.SpecificAssetId = { + ...this.readHasSemantic(node), + name: this.getTextContent('./aas:name', node), + value: this.getTextContent('./aas:value', node), + externalSubjectId, + }; + + return value; } private readAssetAdministrationShells(): aas.AssetAdministrationShell[] { const shells: aas.AssetAdministrationShell[] = []; for (const node of this.selectNodes( - '/aas:aasenv/aas:assetAdministrationShells/aas:assetAdministrationShell', + '/aas:environment/aas:assetAdministrationShells/aas:assetAdministrationShell', this.document, )) { const shell = this.readAssetAdministrationShell(node); @@ -93,9 +152,9 @@ export class XmlReader extends AASReader { } private readAssetAdministrationShell(node: Node): aas.AssetAdministrationShell { - const assetInformation = this.readAssetInformation(); + const assetInformation = this.readAssetInformation(node); if (!assetInformation) { - throw new Error('AssetAdministrationShell.asset'); + throw new Error('AssetAdministrationShell.assetInformation'); } const shell: aas.AssetAdministrationShell = { @@ -104,12 +163,12 @@ export class XmlReader extends AASReader { assetInformation, }; - const submodels = this.readReferences('./aas:submodelRefs/aas:submodelRef', node); + const submodels = this.readReferences('./aas:submodels/aas:reference', node); if (submodels.length > 0) { shell.submodels = submodels; } - const administration = this.readAdministrationInformation('./aas:administration', node); + const administration = this.readAdministrativeInformation(this.selectNode('./aas:administration', node)); if (administration) { shell.administration = administration; } @@ -117,14 +176,9 @@ export class XmlReader extends AASReader { return shell; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - private readAdministrationInformation(path: string, node: Node): aas.AdministrativeInformation | undefined { - return undefined; - } - private readSubmodels(): aas.Submodel[] { const submodels: aas.Submodel[] = []; - for (const node of this.selectNodes('/aas:aasenv/aas:submodels/aas:submodel', this.document)) { + for (const node of this.selectNodes('/aas:environment/aas:submodels/aas:submodel', this.document)) { const submodel = this.readSubmodel(node); submodels.push(submodel); } @@ -135,7 +189,7 @@ export class XmlReader extends AASReader { private readSubmodel(node: Node): aas.Submodel { const submodel: aas.Submodel = { ...this.readIdentifiable(node), - ...this.readHaSemantic(node), + ...this.readHasSemantic(node), ...this.readQualifiable(node), ...this.readHasKind(node), ...this.readHasDataSpecification(node), @@ -150,6 +204,7 @@ export class XmlReader extends AASReader { }, ], }); + if (submodelElements.length > 0) { submodel.submodelElements = submodelElements; } @@ -159,19 +214,7 @@ export class XmlReader extends AASReader { private readSubmodelElements(node: Node, parent?: aas.Reference): aas.SubmodelElement[] { const submodelElements: aas.SubmodelElement[] = []; - for (const child of this.selectNodes('./aas:submodelElements/aas:submodelElement/*[1]', node)) { - const submodelElement = this.readSubmodelElement(child, parent); - if (submodelElement) { - submodelElements.push(submodelElement); - } - } - - return submodelElements; - } - - private readCollectionValue(node: Node, parent?: aas.Reference): aas.SubmodelElement[] { - const submodelElements: aas.SubmodelElement[] = []; - for (const child of this.selectNodes('./aas:value/aas:submodelElement/*[1]', node)) { + for (const child of this.selectNodes('./aas:submodelElements/*', node)) { const submodelElement = this.readSubmodelElement(child, parent); if (submodelElement) { submodelElements.push(submodelElement); @@ -215,6 +258,9 @@ export class XmlReader extends AASReader { case 'SubmodelElementCollection': submodelElement = this.readSubmodelElementCollection(node, parent); break; + case 'SubmodelElementList': + submodelElement = this.readSubmodelElementList(node, parent); + break; default: break; } @@ -222,20 +268,124 @@ export class XmlReader extends AASReader { return submodelElement; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars private readAnnotatedRelationshipElement(node: Node, parent?: aas.Reference): aas.AnnotatedRelationshipElement { - throw new Error('Method not implemented.'); + const base = this.readSubmodelElementType(node, parent); + const first = this.readReference(this.selectNode('./aas:first', node)); + if (!first) { + throw new Error('RelationshipElement.first'); + } + + const second = this.readReference(this.selectNode('./aas:second', node)); + if (!second) { + throw new Error('RelationshipElement.second'); + } + + const annotations = this.readAnnotations(node, parent ? this.createReference(parent, base) : undefined); + + return { + ...base, + first, + second, + annotations, + }; + } + + private readAnnotations(node: Node, parent?: aas.Reference): aas.DataElement[] { + const dataElements: aas.DataElement[] = []; + for (const child of this.selectNodes('./aas:annotations/*', node)) { + const dataElement = this.readDataElement(child, parent); + if (dataElement) { + dataElements.push(dataElement); + } + } + + return dataElements; + } + + private readDataElement(node: Node, parent?: aas.Reference): aas.DataElement | undefined { + let dataElement: aas.DataElement | undefined; + const modelType = this.getModelTypeFromLocalName(node); + switch (modelType) { + case 'Blob': + dataElement = this.readBlob(node, parent); + break; + case 'File': + dataElement = this.readFile(node, parent); + break; + case 'MultiLanguageProperty': + dataElement = this.readMultiLanguageProperty(node, parent); + break; + case 'Property': + dataElement = this.readProperty(node, parent); + break; + case 'Range': + dataElement = this.readRange(node, parent); + break; + case 'ReferenceElement': + dataElement = this.readReferenceElement(node, parent); + break; + default: + break; + } + + return dataElement; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars private readBasicEventElement(node: Node, parent?: aas.Reference): aas.BasicEventElement { - throw new Error('Method not implemented.'); + const observed = this.readReference(this.selectNode('./aas:observed', node)); + if (!observed) { + throw new Error('BasicEventElement.observed'); + } + + const direction = this.selectTextContent('./aas:direction', node) as aas.Direction | undefined; + if (!direction) { + throw new Error('BasicEventElement.direction'); + } + + const state = this.selectTextContent('./aas:state', node) as aas.StateOfEvent | undefined; + if (!state) { + throw new Error('BasicEventElement.state'); + } + + const basicEvent: aas.BasicEventElement = { + ...this.readSubmodelElementType(node, parent), + observed, + direction, + state, + }; + + const messageTopic = this.selectTextContent('./aas:messageTopic', node); + if (messageTopic) { + basicEvent.messageTopic = messageTopic; + } + + const messageBroker = this.readReference(this.selectNode('./aas:messageBroker', node)); + if (messageBroker) { + basicEvent.messageBroker = messageBroker; + } + + const lastUpdate = this.selectTextContent('./aas:lastUpdate', node); + if (lastUpdate) { + basicEvent.lastUpdate = lastUpdate; + } + + const minInterval = this.selectTextContent('./aas:minInterval', node); + if (minInterval) { + basicEvent.minInterval = minInterval; + } + + const maxInterval = this.selectTextContent('./aas:maxInterval', node); + if (maxInterval) { + basicEvent.maxInterval = maxInterval; + } + + return basicEvent; } private readBlob(node: Node, parent?: aas.Reference): aas.Blob { - const contentType = this.selectNode('./aas:mimeType', node)?.textContent; + const contentType = this.selectNode('./aas:contentType', node)?.textContent; if (!contentType) { - throw new Error('File.mimetype'); + throw new Error('File.contentType'); } const blob: aas.Blob = { @@ -261,6 +411,29 @@ export class XmlReader extends AASReader { return collection; } + private readSubmodelElementList(node: Node, parent?: aas.Reference): aas.SubmodelElementList { + const base = this.readSubmodelElementType(node, parent); + const list: aas.SubmodelElementList = { + ...base, + value: this.readCollectionValue(node, parent ? this.createReference(parent, base) : undefined), + typeValueListElement: this.getTextContent('./aas:typeValueListElement', node) as aas.AasSubmodelElements, + }; + + return list; + } + + private readCollectionValue(node: Node, parent?: aas.Reference): aas.SubmodelElement[] { + const submodelElements: aas.SubmodelElement[] = []; + for (const child of this.selectNodes('./aas:value/*', node)) { + const submodelElement = this.readSubmodelElement(child, parent); + if (submodelElement) { + submodelElements.push(submodelElement); + } + } + + return submodelElements; + } + private readProperty(node: Node, parent?: aas.Reference): aas.Property { const valueNode = this.selectNode('./aas:value', node); let value = valueNode?.textContent; @@ -268,7 +441,7 @@ export class XmlReader extends AASReader { const valueTypeNode = this.selectNode('./aas:valueType', node); let valueType: aas.DataTypeDefXsd | undefined; if (valueTypeNode?.textContent) { - valueType = this.toDataTypeDefXsd(valueTypeNode.textContent); + valueType = valueTypeNode.textContent as aas.DataTypeDefXsd; } if (!valueType && value != null) { @@ -288,14 +461,41 @@ export class XmlReader extends AASReader { return property; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars private readRange(node: Node, parent?: aas.Reference): aas.Range { - throw new Error('Method not implemented.'); + const range: aas.Range = { + ...this.readSubmodelElementType(node, parent), + valueType: this.getTextContent('./aas:valueType', node) as aas.DataTypeDefXsd, + }; + + const min = this.selectTextContent('./aas:min', node); + if (min) { + range.min = min; + } + + const max = this.selectTextContent('./aas:max', node); + if (max) { + range.max = max; + } + + return range; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars private readRelationshipElement(node: Node, parent?: aas.Reference): aas.RelationshipElement { - throw new Error('Method not implemented.'); + const first = this.readReference(this.selectNode('./aas:first', node)); + if (!first) { + throw new Error('RelationshipElement.first'); + } + + const second = this.readReference(this.selectNode('./aas:second', node)); + if (!second) { + throw new Error('RelationshipElement.second'); + } + + return { + ...this.readSubmodelElementType(node, parent), + first, + second, + }; } private readFile(node: Node, parent?: aas.Reference): aas.File { @@ -323,14 +523,22 @@ export class XmlReader extends AASReader { } private readReferenceElement(node: Node, parent?: aas.Reference): aas.ReferenceElement { - const value = this.getReference(node); - return { ...this.readSubmodelElementType(node, parent), value }; + const reference: aas.ReferenceElement = { + ...this.readSubmodelElementType(node, parent), + }; + + const value = this.readReference(this.selectNode('./aas:value', node)); + if (value) { + reference.value = value; + } + + return reference; } private readSubmodelElementType(node: Node, parent?: aas.Reference): aas.SubmodelElement { return { ...this.readReferable(node, undefined, parent), - ...this.readHaSemantic(node), + ...this.readHasSemantic(node), ...this.readHasKind(node), ...this.readHasDataSpecification(node), ...this.readQualifiable(node), @@ -338,9 +546,10 @@ export class XmlReader extends AASReader { } private readIdentifiable(node: Node): aas.Identifiable { - const id = this.readIdentifier(node); + const id = this.getTextContent('./aas:id', node); const identifiable: aas.Identifiable = { ...this.readReferable(node, id), id }; - const administration = this.readAdministrativeInformation(node); + + const administration = this.readAdministrativeInformation(this.selectNode('./aas:administration', node)); if (administration) { identifiable.administration = administration; } @@ -349,12 +558,13 @@ export class XmlReader extends AASReader { } private readReferable(node: Node, id?: string, parent?: aas.Reference): aas.Referable { - let idShort = this.selectNode('./aas:idShort', node)?.textContent; + let idShort = this.selectTextContent('./aas:idShort', node); if (!idShort) { idShort = id ? this.createIdShort(id) : ''; } const referable: aas.Referable = { + ...this.readHasExtensions(node), idShort, modelType: this.getModelTypeFromLocalName(node), }; @@ -363,7 +573,7 @@ export class XmlReader extends AASReader { referable.parent = parent; } - const category = this.selectNode('./aas:category', node)?.textContent as aas.Category | undefined; + const category = this.selectTextContent('./aas:category', node) as aas.Category | undefined; if (category) { referable.category = category; } @@ -371,13 +581,36 @@ export class XmlReader extends AASReader { return referable; } - private readHaSemantic(node: Node): aas.HasSemantic { - const semanticId = this.readReference('./aas:semanticId', node); + private readHasExtensions(node: Node): aas.HasExtensions { + const values: aas.HasExtensions = {}; + const extensions: aas.Extension[] = []; + for (const child of this.selectNodes('./aas:extensions/aas:extension', node)) { + extensions.push(this.readExtension(child)); + } + + if (extensions.length > 0) { + values.extensions = extensions; + } + + return values; + } + + private readExtension(node: Node): aas.Extension { + const extension: aas.Extension = { + ...this.readHasSemantic(node), + name: this.getTextContent('./aas:name', node), + }; + + return extension; + } + + private readHasSemantic(node: Node): aas.HasSemantic { + const semanticId = this.readReference(this.selectNode('./aas:semanticId', node)); return semanticId ? { semanticId } : {}; } private readHasKind(node: Node): aas.HasKind { - let kind = this.selectNode('aas:kind', node)?.textContent as aas.ModellingKind; + let kind = this.selectNode('./aas:kind', node)?.textContent as aas.ModellingKind; if (!kind) { kind = 'Instance'; } @@ -386,36 +619,54 @@ export class XmlReader extends AASReader { } private readHasDataSpecification(node: Node): aas.HasDataSpecification { + const value: aas.HasDataSpecification = {}; + const parent = this.selectNode('./aas:embeddedDataSpecifications', node); + if (!parent) { + return value; + } + const embeddedDataSpecifications: aas.EmbeddedDataSpecification[] = []; - for (const child of this.selectNodes('./aas:embeddedDataSpecification', node)) { - const dataSpecification = - this.readReference('./aas:hasDataSpecification', child) ?? - this.readReference('./aas:dataSpecification', child); + for (const child of this.selectNodes('./aas:embeddedDataSpecification', parent)) { + embeddedDataSpecifications.push(this.readEmbeddedDataSpecification(child)); + } - if (!dataSpecification) { - throw new Error('EmbeddedDataSpecification.dataSpecification'); - } + if (embeddedDataSpecifications.length > 0) { + value.embeddedDataSpecifications = embeddedDataSpecifications; + } - const dataSpecificationContent = this.readDataSpecificationContent(child); - if (dataSpecificationContent) { - embeddedDataSpecifications.push({ dataSpecification, dataSpecificationContent }); - } + return value; + } + + private readEmbeddedDataSpecification(node: Node): aas.EmbeddedDataSpecification { + const dataSpecification = this.readReference(this.selectNode('./aas:dataSpecification', node)); + if (!dataSpecification) { + throw new Error('EmbeddedDataSpecification.dataSpecification'); } - return embeddedDataSpecifications.length > 0 ? { embeddedDataSpecifications } : {}; + const dataSpecificationContent = this.readDataSpecificationContent(node); + if (!dataSpecificationContent) { + throw new Error('EmbeddedDataSpecification.dataSpecificationContent'); + } + + const value: aas.EmbeddedDataSpecification = { + dataSpecification: dataSpecification, + dataSpecificationContent: dataSpecificationContent, + }; + + return value; } - private readDataSpecificationContent(parent: Node): aas.DataSpecificationContent | undefined { - const node = this.selectNode('./aas:dataSpecificationContent/aas:dataSpecificationIEC61360', parent); - if (node) { - return this.readDataSpecificationIEC61360(node); + private readDataSpecificationContent(node: Node): aas.DataSpecificationContent | undefined { + const child = this.selectNode('./aas:dataSpecificationContent/aas:dataSpecificationIEC61360', node); + if (child) { + return this.readDataSpecificationIEC61360(child); } return undefined; } private readDataSpecificationIEC61360(node: Node): aas.DataSpecificationIEC61360 { - const preferredName = this.readLangString(`./${this.iec61360}:preferredName`, node); + const preferredName = this.readLangString('./aas:preferredName/aas:langStringPreferredNameTypeIec61360', node); if (!preferredName) { throw new Error('DataSpecificationIEC61360Content.preferredName'); } @@ -425,57 +676,57 @@ export class XmlReader extends AASReader { preferredName, }; - const shortName = this.readLangString(`./${this.iec61360}:shortName`, node); + const shortName = this.readLangString('./aas:shortName/langStringShortNameTypeIec61360', node); if (shortName) { dataSpecification.shortName = shortName; } - const unit = this.selectNode(`./${this.iec61360}:unit`, node)?.textContent; + const unit = this.selectTextContent('./aas:unit', node); if (unit) { dataSpecification.unit = unit; } - const unitId = this.readReference(`./${this.iec61360}:unitId`, node); + const unitId = this.readReference(this.selectNode('./aas:unitId', node)); if (unit) { dataSpecification.unitId = unitId; } - const sourceOfDefinition = this.selectNode(`./${this.iec61360}:sourceOfDefinition`, node)?.textContent; + const sourceOfDefinition = this.selectTextContent('./aas:sourceOfDefinition', node); if (sourceOfDefinition) { dataSpecification.sourceOfDefinition = sourceOfDefinition; } - const symbol = this.selectNode(`./${this.iec61360}:symbol`, node)?.textContent; + const symbol = this.selectTextContent('./aas:symbol', node); if (symbol) { dataSpecification.symbol = symbol; } - const dataType = this.selectNode(`./${this.iec61360}:dataType`, node)?.textContent as aas.DataTypeIEC61360; + const dataType = this.selectTextContent('./aas:dataType', node) as aas.DataTypeIEC61360; if (dataType) { dataSpecification.dataType = dataType; } - const definition = this.readLangString(`./${this.iec61360}:definition`, node); + const definition = this.readLangString('./aas:definition/langStringDefinitionTypeIec61360', node); if (definition) { dataSpecification.definition = definition; } - const valueFormat = this.selectNode(`./${this.iec61360}:valueFormat`, node)?.textContent; + const valueFormat = this.selectTextContent('./aas:valueFormat', node); if (valueFormat) { dataSpecification.valueFormat = valueFormat; } - const valueList = this.readValueList(`./${this.iec61360}:valueList`, node); + const valueList = this.readValueList(this.selectNode('./aas:valueList/valueReferencePairs', node)); if (valueList) { dataSpecification.valueList = valueList; } - const value = this.selectNode(`./${this.iec61360}:value`, node)?.textContent; + const value = this.selectTextContent('./aas:value', node); if (value) { dataSpecification.value = value; } - const levelType = this.selectNode(`./${this.iec61360}:levelType`, node)?.textContent as aas.LevelType; + const levelType = this.selectTextContent('./aas:levelType', node) as aas.LevelType; if (levelType) { dataSpecification.levelType = levelType; } @@ -483,14 +734,45 @@ export class XmlReader extends AASReader { return dataSpecification; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - private readValueList(path: string, node: Node): aas.ValueList | undefined { - return undefined; + private readValueList(node: Node | undefined): aas.ValueList | undefined { + if (!node) { + return undefined; + } + + const valueReferencePairs: aas.ValueReferencePair[] = []; + for (const child of this.selectNodes('./aas:valueReferencePair', node)) { + valueReferencePairs.push(this.readReferenceValuePairs(child)); + } + + const value: aas.ValueList = { + valueReferencePairs, + }; + + return value; + } + + private readReferenceValuePairs(node: Node): aas.ValueReferencePair { + const value = this.selectTextContent('./aas:value', node); + if (!value) { + throw new Error('ValueReferencePair.value'); + } + + const valueId = this.readReference(this.selectNode('./aas:valueId', node)); + if (!valueId) { + throw new Error('ValueReferencePair.valueId'); + } + + const pair: aas.ValueReferencePair = { + value: value, + valueId: valueId, + }; + + return pair; } private readQualifiable(node: Node): aas.Qualifiable { const qualifiable: aas.Qualifiable = {}; - const qualifiers = this.readQualifiers('./aas.qualifier', node); + const qualifiers = this.readQualifiers('./aas:qualifier', node); if (qualifiers) { qualifiable.qualifiers = qualifiers; } @@ -508,74 +790,76 @@ export class XmlReader extends AASReader { return qualifiers; } - private readReference(path: string, parent: Node): aas.Reference | undefined { - let reference: aas.Reference | undefined; - const node = this.selectNode(path, parent); - if (node) { - reference = { type: 'ModelReference', keys: [] }; - for (const keyNode of this.selectNodes('./aas:keys/aas:key', node)) { - reference.keys.push(this.getKey(keyNode)); - } + private readReference(node: Node | undefined): aas.Reference | undefined { + if (!node) { + return undefined; + } + + const reference: aas.Reference = { + type: this.getTextContent('./aas:type', node) as aas.ReferenceTypes, + keys: [], + }; + + for (const keyNode of this.selectNodes('./aas:keys/aas:key', node)) { + reference.keys.push(this.getKey(keyNode)); } return reference; } - private readReferences(path: string, parent: Node): aas.Reference[] { + private readReferences(expression: string, parent: Node): aas.Reference[] { const references: aas.Reference[] = []; - for (const node of this.selectNodes(path, parent)) { - const reference: aas.Reference = { type: 'ModelReference', keys: [] }; - for (const keyNode of this.selectNodes('./aas:keys/aas:key', node)) { - reference.keys.push(this.getKey(keyNode)); + for (const node of this.selectNodes(expression, parent)) { + const reference = this.readReference(node); + if (reference) { + references.push(reference); } - - references.push(reference); } return references; } - private readAdministrativeInformation(node: Node): aas.AdministrativeInformation | undefined { - let value: aas.AdministrativeInformation | undefined; - const version = this.selectNode('./aas.administration/aas.version', node)?.textContent; - const revision = this.selectNode('./aas.administration/aas.revision', node)?.textContent; - if (version || revision) { - value = {}; - if (version) { - value.version = version; - } + private readAdministrativeInformation(node: Node | undefined): aas.AdministrativeInformation | undefined { + if (!node) { + return undefined; + } - if (revision) { - value.revision = revision; - } + const value: aas.AdministrativeInformation = { + ...this.readHasDataSpecification(node), + }; + + const version = this.selectTextContent('./aas:version', node); + if (version) { + value.version = version; + } + const revision = this.selectTextContent('./aas:revision', node); + if (revision) { + value.revision = revision; } return value; } - private readLangString(path: string, parent: Node): aas.LangString[] | undefined { - let langString: aas.LangString[] | undefined; - const content = this.selectNode(path, parent); - if (content) { - langString = []; - for (const node of this.selectNodes('./aas:langString', content)) { - const language = (node as Element).getAttribute('lang')!.toLowerCase(); - const text = node.textContent ?? ''; - langString.push({ language, text }); - } + private readLangString(expression: string, node: Node | undefined): aas.LangString[] | undefined { + if (!node) { + return undefined; + } - if (langString.length === 0 && content.textContent) { - langString.push({ language: 'en', text: content.textContent }); - } + const values: aas.LangString[] = []; + for (const child of this.selectNodes(expression, node)) { + values.push({ + language: this.getTextContent('./aas:language', child), + text: this.getTextContent('./aas:text', child), + }); } - return langString; + return values; } private readConceptDescriptions(): aas.ConceptDescription[] { const conceptDescriptions: aas.ConceptDescription[] = []; for (const node of this.selectNodes( - '/aas:aasenv/aas:conceptDescriptions/aas:conceptDescription', + '/aas:environment/aas:conceptDescriptions/aas:conceptDescription', this.document, )) { conceptDescriptions.push(this.readConceptDescription(node)); @@ -591,8 +875,8 @@ export class XmlReader extends AASReader { }; const isCaseOf: aas.Reference[] = []; - for (const refNode of this.selectNodes('./isCaseOf', node)) { - isCaseOf.push(this.readReference('.', refNode)!); + for (const refNode of this.selectNodes('./aas:isCaseOf/aas:reference', node)) { + isCaseOf.push(this.readReference(refNode)!); } if (isCaseOf.length > 0) { @@ -607,8 +891,21 @@ export class XmlReader extends AASReader { return (localName.charAt(0).toUpperCase() + localName.substring(1)) as aas.ModelType; } - private selectNode(query: string, node: Node): Node | undefined { - return this.select(query, node, true) as Node | undefined; + private selectNode(expression: string, node: Node): Node | undefined { + return this.select(expression, node, true) as Node | undefined; + } + + private selectTextContent(expression: string, node: Node): string | null | undefined { + return (this.select(expression, node, true) as Node)?.textContent; + } + + private getTextContent(expression: string, node: Node): string { + const value = (this.select(expression, node, true) as Node)?.textContent; + if (value == null) { + throw new Error(`${expression} ToDo.`); + } + + return value; } private getNode(query: string, node: Node): Node { @@ -624,104 +921,10 @@ export class XmlReader extends AASReader { return this.select(query, node) as Node[]; } - private getReference(node: Node): aas.Reference { - return { - type: 'ModelReference', - keys: this.selectNodes('./aas:value/aas:keys/aas:key', node).map(item => this.getKey(item)), - }; - } - private getKey(node: Node): aas.Key { - const element = node as Element; return { - type: element.getAttribute('type') as aas.KeyTypes, - value: element.textContent ?? '', + type: this.getTextContent('./aas:type', node) as aas.KeyTypes, + value: this.getTextContent('./aas:value', node), }; } - - private readIdentifier(node: Node): string { - const id = this.selectNode('./aas:identification', node)?.textContent; - if (id == null) { - throw new Error('./aas:identification'); - } - - return id; - } - - private toDataTypeDefXsd(source: string): aas.DataTypeDefXsd { - switch (source) { - case 'anyURI': - return 'xs:anyURI'; - case 'base64Binary': - return 'xs:base64Binary'; - case 'boolean': - return 'xs:boolean'; - case 'byte': - return 'xs:byte'; - case 'Date': - case 'date': - return 'xs:date'; - case 'dateTime': - return 'xs:dateTime'; - case 'dateTimeStamp': - return 'xs:dateTime'; - case 'dayTimeDuration': - return 'xs:duration'; - case 'Decimal': - case 'decimal': - return 'xs:decimal'; - case 'double': - return 'xs:double'; - case 'duration': - return 'xs:duration'; - case 'float': - return 'xs:float'; - case 'gDay': - return 'xs:gDay'; - case 'gMonth': - return 'xs:gMonth'; - case 'gMonthDay': - return 'xs:gMonthDay'; - case 'gYear': - return 'xs:gYear'; - case 'gYearMonth': - return 'xs:gYearMonth'; - case 'hexBinary': - return 'xs:hexBinary'; - case 'int': - return 'xs:int'; - case 'integer': - return 'xs:integer'; - case 'long': - return 'xs:long'; - case 'negativeInteger': - return 'xs:negativeInteger'; - case 'nonNegativeInteger': - return 'xs:nonNegativeInteger'; - case 'nonPositiveInteger': - return 'xs:nonPositiveInteger'; - case 'positiveInteger': - return 'xs:positiveInteger'; - case 'short': - return 'xs:short'; - case 'langString': - case 'String': - case 'string': - return 'xs:string'; - case 'time': - return 'xs:time'; - case 'unsignedByte': - return 'xs:unsignedByte'; - case 'unsignedInt': - return 'xs:unsignedInt'; - case 'unsignedLong': - return 'xs:unsignedLong'; - case 'unsignedShort': - return 'xs:unsignedShort'; - case 'yearMonthDuration': - return 'xs:duration'; - default: - throw new Error(`${source} is an unknown value type.`); - } - } } diff --git a/projects/aas-server/src/app/packages/xml-reader_v2.ts b/projects/aas-server/src/app/packages/xml-reader_v2.ts new file mode 100644 index 00000000..7c19d3d3 --- /dev/null +++ b/projects/aas-server/src/app/packages/xml-reader_v2.ts @@ -0,0 +1,718 @@ +/****************************************************************************** + * + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, + * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft + * zur Foerderung der angewandten Forschung e.V. + * + *****************************************************************************/ + +import { aas, determineType } from 'common'; +import { useNamespaces, XPathSelect } from 'xpath'; +import { DOMParser } from '@xmldom/xmldom'; +import { Logger } from '../logging/logger.js'; +import { AASReader } from './aas-reader.js'; +import { HTMLDocumentElement } from '../types/html-document-element.js'; + +export class XmlReaderV2 extends AASReader { + private readonly select: XPathSelect; + private readonly document: Document; + private iec61360 = 'IEC61360'; + + public constructor( + private readonly logger: Logger, + xmlSource: string | Document, + ) { + super(); + + this.document = typeof xmlSource === 'string' ? new DOMParser().parseFromString(xmlSource) : xmlSource; + this.select = useNamespaces(this.getNamespaces()); + } + + public readEnvironment(): aas.Environment { + const conceptDescriptions = this.readConceptDescriptions(); + const assetAdministrationShells = this.readAssetAdministrationShells(); + const submodels = this.readSubmodels(); + return { assetAdministrationShells, submodels, conceptDescriptions }; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public read(data: unknown): aas.Referable { + throw new Error('Not implemented.'); + } + + private getNamespaces(): { [key: string]: string } { + const nsMap = (this.document.documentElement as HTMLDocumentElement)._nsMap ?? {}; + const namespaces: { [key: string]: string } = {}; + for (const prefix in nsMap) { + const uri = nsMap[prefix]; + if (uri.startsWith('http://www.admin-shell.io/IEC61360/')) { + this.iec61360 = prefix; + namespaces[prefix] = uri; + } else if (uri.startsWith('http://www.admin-shell.io/aas/')) { + namespaces['aas'] = uri; + } + } + + return namespaces; + } + + private readAssetInformation(): aas.AssetInformation { + let assetKind: aas.AssetKind | undefined; + let globalAssetId: string | undefined; + const node = this.selectNode('/aas:aasenv/aas:assets/aas:asset', this.document); + if (node) { + assetKind = this.selectNode('./aas:kind', node)?.textContent as aas.AssetKind; + globalAssetId = this.readIdentifier(node); + } else { + assetKind = 'Instance'; + } + + return { assetKind, globalAssetId }; + } + + private readAssetAdministrationShells(): aas.AssetAdministrationShell[] { + const shells: aas.AssetAdministrationShell[] = []; + for (const node of this.selectNodes( + '/aas:aasenv/aas:assetAdministrationShells/aas:assetAdministrationShell', + this.document, + )) { + const shell = this.readAssetAdministrationShell(node); + shells.push(shell); + } + + return shells; + } + + private readAssetAdministrationShell(node: Node): aas.AssetAdministrationShell { + const assetInformation = this.readAssetInformation(); + if (!assetInformation) { + throw new Error('AssetAdministrationShell.asset'); + } + + const shell: aas.AssetAdministrationShell = { + ...this.readIdentifiable(node), + ...this.readHasDataSpecification(node), + assetInformation, + }; + + const submodels = this.readReferences('./aas:submodelRefs/aas:submodelRef', node); + if (submodels.length > 0) { + shell.submodels = submodels; + } + + const administration = this.readAdministrationInformation('./aas:administration', node); + if (administration) { + shell.administration = administration; + } + + return shell; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private readAdministrationInformation(path: string, node: Node): aas.AdministrativeInformation | undefined { + return undefined; + } + + private readSubmodels(): aas.Submodel[] { + const submodels: aas.Submodel[] = []; + for (const node of this.selectNodes('/aas:aasenv/aas:submodels/aas:submodel', this.document)) { + const submodel = this.readSubmodel(node); + submodels.push(submodel); + } + + return submodels; + } + + private readSubmodel(node: Node): aas.Submodel { + const submodel: aas.Submodel = { + ...this.readIdentifiable(node), + ...this.readHaSemantic(node), + ...this.readQualifiable(node), + ...this.readHasKind(node), + ...this.readHasDataSpecification(node), + }; + + const submodelElements = this.readSubmodelElements(node, { + type: 'ModelReference', + keys: [ + { + type: 'Submodel', + value: submodel.id, + }, + ], + }); + if (submodelElements.length > 0) { + submodel.submodelElements = submodelElements; + } + + return submodel; + } + + private readSubmodelElements(node: Node, parent?: aas.Reference): aas.SubmodelElement[] { + const submodelElements: aas.SubmodelElement[] = []; + for (const child of this.selectNodes('./aas:submodelElements/aas:submodelElement/*[1]', node)) { + const submodelElement = this.readSubmodelElement(child, parent); + if (submodelElement) { + submodelElements.push(submodelElement); + } + } + + return submodelElements; + } + + private readCollectionValue(node: Node, parent?: aas.Reference): aas.SubmodelElement[] { + const submodelElements: aas.SubmodelElement[] = []; + for (const child of this.selectNodes('./aas:value/aas:submodelElement/*[1]', node)) { + const submodelElement = this.readSubmodelElement(child, parent); + if (submodelElement) { + submodelElements.push(submodelElement); + } + } + + return submodelElements; + } + + private readSubmodelElement(node: Node, parent?: aas.Reference): aas.SubmodelElement | undefined { + let submodelElement: aas.SubmodelElement | undefined; + const modelType = this.getModelTypeFromLocalName(node); + switch (modelType) { + case 'AnnotatedRelationshipElement': + submodelElement = this.readAnnotatedRelationshipElement(node, parent); + break; + case 'BasicEventElement': + submodelElement = this.readBasicEventElement(node, parent); + break; + case 'Blob': + submodelElement = this.readBlob(node, parent); + break; + case 'File': + submodelElement = this.readFile(node, parent); + break; + case 'MultiLanguageProperty': + submodelElement = this.readMultiLanguageProperty(node, parent); + break; + case 'Property': + submodelElement = this.readProperty(node, parent); + break; + case 'Range': + submodelElement = this.readRange(node, parent); + break; + case 'ReferenceElement': + submodelElement = this.readReferenceElement(node, parent); + break; + case 'RelationshipElement': + submodelElement = this.readRelationshipElement(node, parent); + break; + case 'SubmodelElementCollection': + submodelElement = this.readSubmodelElementCollection(node, parent); + break; + default: + break; + } + + return submodelElement; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private readAnnotatedRelationshipElement(node: Node, parent?: aas.Reference): aas.AnnotatedRelationshipElement { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private readBasicEventElement(node: Node, parent?: aas.Reference): aas.BasicEventElement { + throw new Error('Method not implemented.'); + } + + private readBlob(node: Node, parent?: aas.Reference): aas.Blob { + const contentType = this.selectNode('./aas:mimeType', node)?.textContent; + if (!contentType) { + throw new Error('File.mimetype'); + } + + const blob: aas.Blob = { + ...this.readSubmodelElementType(node, parent), + contentType, + }; + + const value = this.selectNode('./aas:value', node)?.textContent; + if (value) { + blob.value = value; + } + + return blob; + } + + private readSubmodelElementCollection(node: Node, parent?: aas.Reference): aas.SubmodelElementCollection { + const base = this.readSubmodelElementType(node, parent); + const collection: aas.SubmodelElementCollection = { + ...base, + value: this.readCollectionValue(node, parent ? this.createReference(parent, base) : undefined), + }; + + return collection; + } + + private readProperty(node: Node, parent?: aas.Reference): aas.Property { + const valueNode = this.selectNode('./aas:value', node); + let value = valueNode?.textContent; + + const valueTypeNode = this.selectNode('./aas:valueType', node); + let valueType: aas.DataTypeDefXsd | undefined; + if (valueTypeNode?.textContent) { + valueType = this.toDataTypeDefXsd(valueTypeNode.textContent); + } + + if (!valueType && value != null) { + valueType = determineType(value); + } + + if (!valueType) { + valueType = 'xs:string'; + value = '!!! Undefined value type !!!'; + } + + const property: aas.Property = { ...this.readSubmodelElementType(node, parent), valueType }; + if (value) { + property.value = value; + } + + return property; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private readRange(node: Node, parent?: aas.Reference): aas.Range { + throw new Error('Method not implemented.'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private readRelationshipElement(node: Node, parent?: aas.Reference): aas.RelationshipElement { + throw new Error('Method not implemented.'); + } + + private readFile(node: Node, parent?: aas.Reference): aas.File { + let contentType = this.selectNode('./aas:mimeType', node)?.textContent; + if (!contentType) { + contentType = ''; + } + + const file: aas.File = { + ...this.readSubmodelElementType(node, parent), + contentType, + }; + + const value = this.selectNode('./aas:value', node)?.textContent; + if (value) { + file.value = value; + } + + return file; + } + + private readMultiLanguageProperty(node: Node, parent?: aas.Reference): aas.MultiLanguageProperty { + const langString = this.readLangString('./aas:value', node) ?? []; + return { ...this.readSubmodelElementType(node, parent), value: langString }; + } + + private readReferenceElement(node: Node, parent?: aas.Reference): aas.ReferenceElement { + const value = this.getReference(node); + return { ...this.readSubmodelElementType(node, parent), value }; + } + + private readSubmodelElementType(node: Node, parent?: aas.Reference): aas.SubmodelElement { + return { + ...this.readReferable(node, undefined, parent), + ...this.readHaSemantic(node), + ...this.readHasKind(node), + ...this.readHasDataSpecification(node), + ...this.readQualifiable(node), + }; + } + + private readIdentifiable(node: Node): aas.Identifiable { + const id = this.readIdentifier(node); + const identifiable: aas.Identifiable = { ...this.readReferable(node, id), id }; + const administration = this.readAdministrativeInformation(node); + if (administration) { + identifiable.administration = administration; + } + + return identifiable; + } + + private readReferable(node: Node, id?: string, parent?: aas.Reference): aas.Referable { + let idShort = this.selectNode('./aas:idShort', node)?.textContent; + if (!idShort) { + idShort = id ? this.createIdShort(id) : ''; + } + + const referable: aas.Referable = { + idShort, + modelType: this.getModelTypeFromLocalName(node), + }; + + if (parent) { + referable.parent = parent; + } + + const category = this.selectNode('./aas:category', node)?.textContent as aas.Category | undefined; + if (category) { + referable.category = category; + } + + return referable; + } + + private readHaSemantic(node: Node): aas.HasSemantic { + const semanticId = this.readReference('./aas:semanticId', node); + return semanticId ? { semanticId } : {}; + } + + private readHasKind(node: Node): aas.HasKind { + let kind = this.selectNode('aas:kind', node)?.textContent as aas.ModellingKind; + if (!kind) { + kind = 'Instance'; + } + + return { kind }; + } + + private readHasDataSpecification(node: Node): aas.HasDataSpecification { + const embeddedDataSpecifications: aas.EmbeddedDataSpecification[] = []; + for (const child of this.selectNodes('./aas:embeddedDataSpecification', node)) { + const dataSpecification = + this.readReference('./aas:hasDataSpecification', child) ?? + this.readReference('./aas:dataSpecification', child); + + if (!dataSpecification) { + throw new Error('EmbeddedDataSpecification.dataSpecification'); + } + + const dataSpecificationContent = this.readDataSpecificationContent(child); + if (dataSpecificationContent) { + embeddedDataSpecifications.push({ dataSpecification, dataSpecificationContent }); + } + } + + return embeddedDataSpecifications.length > 0 ? { embeddedDataSpecifications } : {}; + } + + private readDataSpecificationContent(parent: Node): aas.DataSpecificationContent | undefined { + const node = this.selectNode('./aas:dataSpecificationContent/aas:dataSpecificationIEC61360', parent); + if (node) { + return this.readDataSpecificationIEC61360(node); + } + + return undefined; + } + + private readDataSpecificationIEC61360(node: Node): aas.DataSpecificationIEC61360 { + const preferredName = this.readLangString(`./${this.iec61360}:preferredName`, node); + if (!preferredName) { + throw new Error('DataSpecificationIEC61360Content.preferredName'); + } + + const dataSpecification: aas.DataSpecificationIEC61360 = { + modelType: this.getModelTypeFromLocalName(node), + preferredName, + }; + + const shortName = this.readLangString(`./${this.iec61360}:shortName`, node); + if (shortName) { + dataSpecification.shortName = shortName; + } + + const unit = this.selectNode(`./${this.iec61360}:unit`, node)?.textContent; + if (unit) { + dataSpecification.unit = unit; + } + + const unitId = this.readReference(`./${this.iec61360}:unitId`, node); + if (unit) { + dataSpecification.unitId = unitId; + } + + const sourceOfDefinition = this.selectNode(`./${this.iec61360}:sourceOfDefinition`, node)?.textContent; + if (sourceOfDefinition) { + dataSpecification.sourceOfDefinition = sourceOfDefinition; + } + + const symbol = this.selectNode(`./${this.iec61360}:symbol`, node)?.textContent; + if (symbol) { + dataSpecification.symbol = symbol; + } + + const dataType = this.selectNode(`./${this.iec61360}:dataType`, node)?.textContent as aas.DataTypeIEC61360; + if (dataType) { + dataSpecification.dataType = dataType; + } + + const definition = this.readLangString(`./${this.iec61360}:definition`, node); + if (definition) { + dataSpecification.definition = definition; + } + + const valueFormat = this.selectNode(`./${this.iec61360}:valueFormat`, node)?.textContent; + if (valueFormat) { + dataSpecification.valueFormat = valueFormat; + } + + const valueList = this.readValueList(`./${this.iec61360}:valueList`, node); + if (valueList) { + dataSpecification.valueList = valueList; + } + + const value = this.selectNode(`./${this.iec61360}:value`, node)?.textContent; + if (value) { + dataSpecification.value = value; + } + + const levelType = this.selectNode(`./${this.iec61360}:levelType`, node)?.textContent as aas.LevelType; + if (levelType) { + dataSpecification.levelType = levelType; + } + + return dataSpecification; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + private readValueList(path: string, node: Node): aas.ValueList | undefined { + return undefined; + } + + private readQualifiable(node: Node): aas.Qualifiable { + const qualifiable: aas.Qualifiable = {}; + const qualifiers = this.readQualifiers('./aas:qualifier', node); + if (qualifiers) { + qualifiable.qualifiers = qualifiers; + } + + return qualifiable; + } + + private readQualifiers(path: string, parent: Node): aas.Qualifier[] | undefined { + let qualifiers: aas.Qualifier[] | undefined; + const node = this.selectNode(path, parent); + if (node) { + qualifiers = []; // ToDo. + } + + return qualifiers; + } + + private readReference(path: string, parent: Node): aas.Reference | undefined { + let reference: aas.Reference | undefined; + const node = this.selectNode(path, parent); + if (node) { + reference = { type: 'ModelReference', keys: [] }; + for (const keyNode of this.selectNodes('./aas:keys/aas:key', node)) { + reference.keys.push(this.getKey(keyNode)); + } + } + + return reference; + } + + private readReferences(path: string, parent: Node): aas.Reference[] { + const references: aas.Reference[] = []; + for (const node of this.selectNodes(path, parent)) { + const reference: aas.Reference = { type: 'ModelReference', keys: [] }; + for (const keyNode of this.selectNodes('./aas:keys/aas:key', node)) { + reference.keys.push(this.getKey(keyNode)); + } + + references.push(reference); + } + + return references; + } + + private readAdministrativeInformation(node: Node): aas.AdministrativeInformation | undefined { + let value: aas.AdministrativeInformation | undefined; + const version = this.selectNode('./aas:administration/aas:version', node)?.textContent; + const revision = this.selectNode('./aas:administration/aas:revision', node)?.textContent; + if (version || revision) { + value = {}; + if (version) { + value.version = version; + } + + if (revision) { + value.revision = revision; + } + } + + return value; + } + + private readLangString(path: string, parent: Node): aas.LangString[] | undefined { + let langString: aas.LangString[] | undefined; + const content = this.selectNode(path, parent); + if (content) { + langString = []; + for (const node of this.selectNodes('./aas:langString', content)) { + const language = (node as Element).getAttribute('lang')!.toLowerCase(); + const text = node.textContent ?? ''; + langString.push({ language, text }); + } + + if (langString.length === 0 && content.textContent) { + langString.push({ language: 'en', text: content.textContent }); + } + } + + return langString; + } + + private readConceptDescriptions(): aas.ConceptDescription[] { + const conceptDescriptions: aas.ConceptDescription[] = []; + for (const node of this.selectNodes( + '/aas:aasenv/aas:conceptDescriptions/aas:conceptDescription', + this.document, + )) { + conceptDescriptions.push(this.readConceptDescription(node)); + } + + return conceptDescriptions; + } + + private readConceptDescription(node: Node): aas.ConceptDescription { + const conceptDescription: aas.ConceptDescription = { + ...this.readIdentifiable(node), + ...this.readHasDataSpecification(node), + }; + + const isCaseOf: aas.Reference[] = []; + for (const refNode of this.selectNodes('./aas:isCaseOf', node)) { + isCaseOf.push(this.readReference('.', refNode)!); + } + + if (isCaseOf.length > 0) { + conceptDescription.isCaseOf = isCaseOf; + } + + return conceptDescription; + } + + private getModelTypeFromLocalName(node: Node): aas.ModelType { + const localName: string = (node as Element).localName; + return (localName.charAt(0).toUpperCase() + localName.substring(1)) as aas.ModelType; + } + + private selectNode(query: string, node: Node): Node | undefined { + return this.select(query, node, true) as Node | undefined; + } + + private getNode(query: string, node: Node): Node { + const result = this.select(query, node, true); + if (!result) { + throw new Error(`Query '${query}' returns no result.`); + } + + return result as Node; + } + + private selectNodes(query: string, node: Node): Node[] { + return this.select(query, node) as Node[]; + } + + private getReference(node: Node): aas.Reference { + return { + type: 'ModelReference', + keys: this.selectNodes('./aas:value/aas:keys/aas:key', node).map(item => this.getKey(item)), + }; + } + + private getKey(node: Node): aas.Key { + const element = node as Element; + return { + type: element.getAttribute('type') as aas.KeyTypes, + value: element.textContent ?? '', + }; + } + + private readIdentifier(node: Node): string { + const id = this.selectNode('./aas:identification', node)?.textContent; + if (id == null) { + throw new Error('./aas:identification'); + } + + return id; + } + + private toDataTypeDefXsd(source: string): aas.DataTypeDefXsd { + switch (source) { + case 'anyURI': + return 'xs:anyURI'; + case 'base64Binary': + return 'xs:base64Binary'; + case 'boolean': + return 'xs:boolean'; + case 'byte': + return 'xs:byte'; + case 'Date': + case 'date': + return 'xs:date'; + case 'dateTime': + return 'xs:dateTime'; + case 'dateTimeStamp': + return 'xs:dateTime'; + case 'dayTimeDuration': + return 'xs:duration'; + case 'Decimal': + case 'decimal': + return 'xs:decimal'; + case 'double': + return 'xs:double'; + case 'duration': + return 'xs:duration'; + case 'float': + return 'xs:float'; + case 'gDay': + return 'xs:gDay'; + case 'gMonth': + return 'xs:gMonth'; + case 'gMonthDay': + return 'xs:gMonthDay'; + case 'gYear': + return 'xs:gYear'; + case 'gYearMonth': + return 'xs:gYearMonth'; + case 'hexBinary': + return 'xs:hexBinary'; + case 'int': + return 'xs:int'; + case 'integer': + return 'xs:integer'; + case 'long': + return 'xs:long'; + case 'negativeInteger': + return 'xs:negativeInteger'; + case 'nonNegativeInteger': + return 'xs:nonNegativeInteger'; + case 'nonPositiveInteger': + return 'xs:nonPositiveInteger'; + case 'positiveInteger': + return 'xs:positiveInteger'; + case 'short': + return 'xs:short'; + case 'langString': + case 'String': + case 'string': + return 'xs:string'; + case 'time': + return 'xs:time'; + case 'unsignedByte': + return 'xs:unsignedByte'; + case 'unsignedInt': + return 'xs:unsignedInt'; + case 'unsignedLong': + return 'xs:unsignedLong'; + case 'unsignedShort': + return 'xs:unsignedShort'; + case 'yearMonthDuration': + return 'xs:duration'; + default: + throw new Error(`${source} is an unknown value type.`); + } + } +} diff --git a/projects/aas-server/src/app/template/template-storage.ts b/projects/aas-server/src/app/template/template-storage.ts index 9e6aa672..6bf6bd3b 100644 --- a/projects/aas-server/src/app/template/template-storage.ts +++ b/projects/aas-server/src/app/template/template-storage.ts @@ -67,7 +67,6 @@ export class TemplateStorage { idShort: template.idShort, endpoint: { type: 'file', address: path }, format: '.json', - template: null, modelType: template.modelType, }; diff --git a/projects/aas-server/src/app/types/html-document-element.ts b/projects/aas-server/src/app/types/html-document-element.ts new file mode 100644 index 00000000..175b2533 --- /dev/null +++ b/projects/aas-server/src/app/types/html-document-element.ts @@ -0,0 +1,3 @@ +export interface HTMLDocumentElement extends HTMLElement { + _nsMap: { [key: string]: string }; +} diff --git a/projects/aas-server/src/app/variable.ts b/projects/aas-server/src/app/variable.ts index 3ba8160b..120f91a5 100644 --- a/projects/aas-server/src/app/variable.ts +++ b/projects/aas-server/src/app/variable.ts @@ -7,7 +7,7 @@ *****************************************************************************/ import { singleton } from 'tsyringe'; -import path from 'path'; +import path from 'path/posix'; @singleton() export class Variable { diff --git a/projects/aas-server/src/test/aas-index/lowdb/lowdb-index.spec.ts b/projects/aas-server/src/test/aas-index/lowdb/lowdb-index.spec.ts index f8e2f892..e5654fac 100644 --- a/projects/aas-server/src/test/aas-index/lowdb/lowdb-index.spec.ts +++ b/projects/aas-server/src/test/aas-index/lowdb/lowdb-index.spec.ts @@ -7,7 +7,7 @@ *****************************************************************************/ import { beforeAll, beforeEach, describe, it, expect, jest } from '@jest/globals'; -import path from 'path'; +import path from 'path/posix'; import fs from 'fs'; import { Low } from 'lowdb'; import { AASCursor, AASDocument, AASDocumentId } from 'common'; diff --git a/projects/aas-server/src/test/aas-index/lowdb/lowdb-query.spec.ts b/projects/aas-server/src/test/aas-index/lowdb/lowdb-query.spec.ts index 3177561d..a5413fb9 100644 --- a/projects/aas-server/src/test/aas-index/lowdb/lowdb-query.spec.ts +++ b/projects/aas-server/src/test/aas-index/lowdb/lowdb-query.spec.ts @@ -7,7 +7,7 @@ *****************************************************************************/ import { describe, beforeEach, it, expect, beforeAll } from '@jest/globals'; -import path from 'path'; +import path from 'path/posix'; import fs from 'fs'; import { LowDbQuery } from '../../../app/aas-index/lowdb/lowdb-query.js'; import { LowDbData, LowDbDocument, LowDbElement } from '../../../app/aas-index/lowdb/lowdb-types.js'; diff --git a/projects/aas-server/src/test/aas-provider/aas-provider.spec.ts b/projects/aas-server/src/test/aas-provider/aas-provider.spec.ts index d17d5623..d6474f55 100644 --- a/projects/aas-server/src/test/aas-provider/aas-provider.spec.ts +++ b/projects/aas-server/src/test/aas-provider/aas-provider.spec.ts @@ -41,7 +41,7 @@ describe('AASProvider', function () { describe('getEndpoints', () => { it('gets the endpoints of all registered AAS containers', async () => { - const endpoints: AASEndpoint[] = [{ name: 'Samples', url: '../assets/samples', type: 'AasxDirectory' }]; + const endpoints: AASEndpoint[] = [{ name: 'Samples', url: '../assets/samples', type: 'FileSystem' }]; index.getEndpoints.mockResolvedValue(endpoints); await expect(aasProvider.getEndpoints()).resolves.toEqual(endpoints); }); diff --git a/projects/aas-server/src/test/application-info.spec.ts b/projects/aas-server/src/test/application-info.spec.ts index 8e02cbba..44adabc6 100644 --- a/projects/aas-server/src/test/application-info.spec.ts +++ b/projects/aas-server/src/test/application-info.spec.ts @@ -8,7 +8,7 @@ import 'reflect-metadata'; import { describe, beforeEach, it, expect } from '@jest/globals'; -import { resolve } from 'path'; +import { resolve } from 'path/posix'; import { ApplicationInfo } from '../app/application-info.js'; import { Logger } from '../app/logging/logger.js'; import { readFile } from 'fs/promises'; diff --git a/projects/aas-server/src/test/assets/aas-example-v3.xml b/projects/aas-server/src/test/assets/aas-example-v3.xml new file mode 100644 index 00000000..36ac7664 --- /dev/null +++ b/projects/aas-server/src/test/assets/aas-example-v3.xml @@ -0,0 +1,5302 @@ + + + + Bosch_NexoPistolGripNutrunner + http://boschrexroth.com/shells/0608842005/917004878 + + Instance + http://dc-qr.com?m=0608842005&s=917004878 + + + + ModelReference + + + Submodel + http://boschrexroth.com/shells/0608842005/917004878/submodels/nameplate + + + + + ModelReference + + + Submodel + http://boschrexroth.com/shells/0608842005/917004878/submodels/document + + + + + ModelReference + + + Submodel + http://boschrexroth.com/shells/0608842005/917004878/submodels/service + + + + + ModelReference + + + Submodel + http://boschrexroth.com/shells/0608842005/917004878/submodels/identification + + + + + + + + + Nameplate + http://boschrexroth.com/shells/0608842005/917004878/submodels/nameplate + Instance + + ExternalReference + + + GlobalReference + https://www.hsu-hh.de/aut/aas/nameplate + + + + + + PARAMETER + ManufacturerName + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO677#002 + + + + xs:string + Bosch Rexroth AG + + + PARAMETER + ManufacturerProductDesignation + + ExternalReference + + + ConceptDescription + 0173-1#02-AAW338#001 + + + + xs:string + Nexo Wi-Fi Cordless Nutrunner + + + PARAMETER + PhysicalAddress + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/physicaladdress + + + + + + PARAMETER + CountryCode + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO730#001 + + + + xs:string + DE + + + PARAMETER + Street + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO128#001 + + + + xs:string + Fornsbacher Straße 92 + + + PARAMETER + Zip + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO129#002 + + + + xs:string + 71540 + + + PARAMETER + CityTown + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO132#001 + + + + xs:string + Murrhardt + + + PARAMETER + StateCounty + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO133#002 + + + + xs:string + Baden-Württemberg + + + + + PARAMETER + ManufacturerProductFamily + + ExternalReference + + + ConceptDescription + 0173-1#02-AAU731#001 + + + + xs:string + Cordless Nutrunner + + + PARAMETER + SerialNumber + + ExternalReference + + + ConceptDescription + 0173-1#02-AAM556#002 + + + + xs:string + 917004878 + + + PARAMETER + BatchNumber + + ExternalReference + + + ConceptDescription + 0173-1#02-AAQ196#001 + + + + xs:string + + + + PARAMETER + ProductCountryOfOrigin + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO841#001 + + + + xs:string + DE + + + PARAMETER + YearOfConstruction + + ExternalReference + + + ConceptDescription + 0173-1#02-AAP906#001 + + + + xs:string + xxxxxxxxxxxxxxxxxxxxxxx + + + PARAMETER + Marking_CE + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/productmarking + + + + + + PARAMETER + CEQualificationPresent + + ExternalReference + + + ConceptDescription + 0173-1#02-BAF053#008 + + + + xs:boolean + 1 + + + PARAMETER + File + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD005#008 + + + + /aasx/Nameplate/marking_ce.png + image/png + + + + + + + Document + http://boschrexroth.com/shells/0608842005/917004878/submodels/document + Instance + + ExternalReference + + + GlobalReference + https://www.hsu-hh.de/aut/aas/document + + + + + + PARAMETER + DeclarationCEMarking + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD001#001 + + + + + + PARAMETER + DocumentType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentType + + + + xs:string + Single + + + PARAMETER + VDI2770_DomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/DomainId + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_IdType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/IdType + + + + xs:string + Primary + + + PARAMETER + DocumentId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId + + + + xs:string + DCTC-30434-002_KOE_M_NN_2019-01-01 + + + PARAMETER + DocumentDomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentIdDomain/DocumentDomainId + + + + xs:string + + + + PARAMETER + VDI2770_Role + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Role + + + + xs:string + Responsible + + + PARAMETER + VDI2770_OrganisationId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationId + + + + xs:string + + + + PARAMETER + VDI2770_OrganisationName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationName + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_OrganisationOfficialName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationOfficialName + + + + xs:string + Bosch Rexroth AG + + + PARAMETER + VDI2770_Description + + + DE + Eine Beschreibung zur Dokumententeile ID. Da eine Sprachangabe nicht möglich ist, sollte die Sprache für dieses Metadatum vor der Lieferung abgestimmt werden. + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description + + + + xs:string + + + + PARAMETER + DocumentPartId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentPartId + + + + xs:string + + + + PARAMETER + DocumentClassification_ClassId + + + DE + eindeutige ID der Klasse in einer Klassifikation + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassId + + + + xs:string + 02-04 + + + PARAMETER + VDI2770_ClassName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassName + + + + xs:string + Zeugnisse, Zertifikate, Bescheinigungen + + + PARAMETER + ClassificationSystem + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassificationSystem + + + + xs:string + VDI2770:2018 + + + PARAMETER + DocumentVersionId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersionId + + + + xs:string + 2019-01-01 + + + PARAMETER + DocumentVersion_LanguageCode + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/LanguageCode + + + + xs:string + de + + + PARAMETER + VDI2770_Title + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Title + + + + xs:string + EG-Konformitätserklärung + + + PARAMETER + VDI2770_Summary + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Summary + + + + xs:string + + + + PARAMETER + VDI2770_Keywords + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Keywords + + + + xs:string + + + + PARAMETER + VDI2770_StatusValue + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/StatusValue + + + + xs:string + Released + + + PARAMETER + VDI2770_SetDate + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/SetDate + + + + xs:string + + + + PARAMETER + VDI2770_Purpose + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Purpose + + + + xs:string + + + + PARAMETER + VDI2770_BasedOnProcedure + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/BasedOnProcedure + + + + xs:string + + + + PARAMETER + VDI2770_Comments + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Comments + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_Type + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ReferencedObjectType + + + + xs:string + Product + + + PARAMETER + VDI2770_ReferencedObject_RefType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/RefType + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_ObjectId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ObjectId + + + + xs:string + + + + PARAMETER + VDI2770_FileId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileId + + + + xs:string + + + + PARAMETER + VDI2770_FileName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileName + + + + xs:string + DCTC-30434-002_KOE_M_NN_2019-01-01.pdf + + + PARAMETER + VDI2770_FileFormat + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileFormat + + + + xs:string + application/pdf + + + PARAMETER + File + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD005#008 + + + + /aasx/Document/DCTC-30434-002_KOE_M_NN_2019-01-01.pdf + application/pdf + + + + + PARAMETER + EU_Declaration_of_Conformity + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD001#001 + + + + + + PARAMETER + DocumentType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentType + + + + xs:string + Single + + + PARAMETER + VDI2770_DomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/DomainId + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_IdType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/IdType + + + + xs:string + Primary + + + PARAMETER + DocumentId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId + + + + xs:string + DCTC-30434-002:2019-01-01 + + + PARAMETER + DocumentDomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentIdDomain/DocumentDomainId + + + + xs:string + + + + PARAMETER + VDI2770_Role + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Role + + + + xs:string + Responsible + + + PARAMETER + VDI2770_OrganisationId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationId + + + + xs:string + + + + PARAMETER + VDI2770_OrganisationName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationName + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_OrganisationOfficialName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationOfficialName + + + + xs:string + Bosch Rexroth AG + + + PARAMETER + VDI2770_Description + + + DE + Eine Beschreibung zur Dokumententeile ID. Da eine Sprachangabe nicht möglich ist, sollte die Sprache für dieses Metadatum vor der Lieferung abgestimmt werden. + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description + + + + xs:string + + + + PARAMETER + DocumentPartId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentPartId + + + + xs:string + + + + PARAMETER + DocumentClassification_ClassId + + + DE + eindeutige ID der Klasse in einer Klassifikation + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassId + + + + xs:string + 02-04 + + + PARAMETER + VDI2770_ClassName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassName + + + + xs:string + Zeugnisse, Zertifikate, Bescheinigungen + + + PARAMETER + ClassificationSystem + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassificationSystem + + + + xs:string + VDI2770:2018 + + + PARAMETER + DocumentVersionId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersionId + + + + xs:string + 2019-01-01 + + + PARAMETER + DocumentVersion_LanguageCode + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/LanguageCode + + + + xs:string + de/en/fr/es/pt/it/pl/cs/sk/hu/bg/da/et/fi/el/hr/lv/lt/nl/ro/sv/sl/ru/zh/ar/id/ja/ko/no/th/tr/vi + + + PARAMETER + VDI2770_Title + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Title + + + + xs:string + EU-Konformitätserklärung (DE) EU Declaration of Conformity (EN) + + + PARAMETER + VDI2770_Summary + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Summary + + + + xs:string + + + + PARAMETER + VDI2770_Keywords + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Keywords + + + + xs:string + + + + PARAMETER + VDI2770_StatusValue + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/StatusValue + + + + xs:string + Released + + + PARAMETER + VDI2770_SetDate + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/SetDate + + + + xs:string + + + + PARAMETER + VDI2770_Purpose + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Purpose + + + + xs:string + + + + PARAMETER + VDI2770_BasedOnProcedure + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/BasedOnProcedure + + + + xs:string + + + + PARAMETER + VDI2770_Comments + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Comments + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_Type + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ReferencedObjectType + + + + xs:string + Product + + + PARAMETER + VDI2770_ReferencedObject_RefType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/RefType + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_ObjectId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ObjectId + + + + xs:string + + + + PARAMETER + VDI2770_FileId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileId + + + + xs:string + + + + PARAMETER + VDI2770_FileName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileName + + + + xs:string + EU_Declaration_of_Conformity.pdf + + + PARAMETER + VDI2770_FileFormat + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileFormat + + + + xs:string + application/pdf + + + PARAMETER + File + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD005#008 + + + + /aasx/Document/EU_Declaration_of_Conformity.pdf + application/pdf + + + + + PARAMETER + Certificat_ISO_9001_2015 + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD001#001 + + + + + + PARAMETER + DocumentType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentType + + + + xs:string + Single + + + PARAMETER + VDI2770_DomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/DomainId + + + + xs:string + + + + PARAMETER + VDI2770_IdType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/IdType + + + + xs:string + Primary + + + PARAMETER + DocumentId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId + + + + xs:string + + + + PARAMETER + DocumentDomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentIdDomain/DocumentDomainId + + + + xs:string + + + + PARAMETER + VDI2770_Role + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Role + + + + xs:string + Responsible + + + PARAMETER + VDI2770_OrganisationId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationId + + + + xs:string + + + + PARAMETER + VDI2770_OrganisationName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationName + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_OrganisationOfficialName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationOfficialName + + + + xs:string + Bosch Rexroth AG + + + PARAMETER + VDI2770_Description + + + DE + Eine Beschreibung zur Dokumententeile ID. Da eine Sprachangabe nicht möglich ist, sollte die Sprache für dieses Metadatum vor der Lieferung abgestimmt werden. + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description + + + + xs:string + + + + PARAMETER + DocumentPartId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentPartId + + + + xs:string + + + + PARAMETER + DocumentClassification_ClassId + + + DE + eindeutige ID der Klasse in einer Klassifikation + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassId + + + + xs:string + 02-04 + + + PARAMETER + VDI2770_ClassName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassName + + + + xs:string + Zeugnisse, Zertifikate, Bescheinigungen + + + PARAMETER + ClassificationSystem + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassificationSystem + + + + xs:string + VDI2770:2018 + + + PARAMETER + DocumentVersionId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersionId + + + + xs:string + + + + PARAMETER + DocumentVersion_LanguageCode + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/LanguageCode + + + + xs:string + de + + + PARAMETER + VDI2770_Title + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Title + + + + xs:string + Zertifikat für Bosch Rexroth AG Normen ISO 9001:2015 + + + PARAMETER + VDI2770_Summary + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Summary + + + + xs:string + + + + PARAMETER + VDI2770_Keywords + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Keywords + + + + xs:string + + + + PARAMETER + VDI2770_StatusValue + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/StatusValue + + + + xs:string + Released + + + PARAMETER + VDI2770_SetDate + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/SetDate + + + + xs:string + + + + PARAMETER + VDI2770_Purpose + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Purpose + + + + xs:string + + + + PARAMETER + VDI2770_BasedOnProcedure + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/BasedOnProcedure + + + + xs:string + + + + PARAMETER + VDI2770_Comments + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Comments + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_Type + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ReferencedObjectType + + + + xs:string + Product + + + PARAMETER + VDI2770_ReferencedObject_RefType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/RefType + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_ObjectId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ObjectId + + + + xs:string + + + + PARAMETER + VDI2770_FileId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileId + + + + xs:string + + + + PARAMETER + VDI2770_FileName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileName + + + + xs:string + DCMC-01001-000_KOB_N_EN_2019-06-14.pdf + + + PARAMETER + VDI2770_FileFormat + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileFormat + + + + xs:string + application/pdf + + + PARAMETER + File + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD005#008 + + + + /aasx/Document/DCMC-01001-000_KOB_N_EN_2019-06-14.pdf + application/pdf + + + + + PARAMETER + 3608870EF2_OperatingInstructions + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD001#001 + + + + + + PARAMETER + DocumentType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentType + + + + xs:string + Single + + + PARAMETER + VDI2770_DomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/DomainId + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_IdType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/IdType + + + + xs:string + Primary + + + PARAMETER + DocumentId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId + + + + xs:string + 3608870EF2 + + + PARAMETER + DocumentDomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentIdDomain/DocumentDomainId + + + + xs:string + + + + PARAMETER + VDI2770_Role + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Role + + + + xs:string + Responsible + + + PARAMETER + VDI2770_OrganisationId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationId + + + + xs:string + + + + PARAMETER + VDI2770_OrganisationName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationName + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_OrganisationOfficialName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationOfficialName + + + + xs:string + Bosch Rexroth AG + + + PARAMETER + VDI2770_Description + + + DE + Eine Beschreibung zur Dokumententeile ID. Da eine Sprachangabe nicht möglich ist, sollte die Sprache für dieses Metadatum vor der Lieferung abgestimmt werden. + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description + + + + xs:string + + + + PARAMETER + DocumentPartId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentPartId + + + + xs:string + + + + PARAMETER + DocumentClassification_ClassId + + + DE + eindeutige ID der Klasse in einer Klassifikation + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassId + + + + xs:string + 03-02 + + + PARAMETER + VDI2770_ClassName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassName + + + + xs:string + Bedienung (DE) Operating Instructions (EN) + + + PARAMETER + ClassificationSystem + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassificationSystem + + + + xs:string + VDI2770:2018 + + + PARAMETER + DocumentVersionId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersionId + + + + xs:string + AC / 01.2018 + + + PARAMETER + DocumentVersion_LanguageCode + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/LanguageCode + + + + xs:string + en-US/​cs-CZ/​de-DE/​es-ES/​fr-FR/​hu-HU/​it-IT/​pl-PL/​pt-BR/​sl-SI/​ru-RU/​zh-CN + + + PARAMETER + VDI2770_Title + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Title + + + + xs:string + NXA, NXP, NXV012T + + + PARAMETER + VDI2770_Summary + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Summary + + + + xs:string + + + + PARAMETER + VDI2770_Keywords + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Keywords + + + + xs:string + + + + PARAMETER + VDI2770_StatusValue + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/StatusValue + + + + xs:string + Released + + + PARAMETER + VDI2770_SetDate + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/SetDate + + + + xs:string + + + + PARAMETER + VDI2770_Purpose + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Purpose + + + + xs:string + + + + PARAMETER + VDI2770_BasedOnProcedure + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/BasedOnProcedure + + + + xs:string + + + + PARAMETER + VDI2770_Comments + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Comments + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_Type + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ReferencedObjectType + + + + xs:string + Product + + + PARAMETER + VDI2770_ReferencedObject_RefType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/RefType + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_ObjectId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ObjectId + + + + xs:string + + + + PARAMETER + VDI2770_FileId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileId + + + + xs:string + + + + PARAMETER + VDI2770_FileName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileName + + + + xs:string + 3608870EF2_AC.pdf + + + PARAMETER + VDI2770_FileFormat + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileFormat + + + + xs:string + application/pdf + + + PARAMETER + File + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD005#008 + + + + /aasx/Document/3608870EF2_AC.pdf + application/pdf + + + + + PARAMETER + 3608870A47_ProjectPlanning_DE + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD001#001 + + + + + + PARAMETER + DocumentType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentType + + + + xs:string + Single + + + PARAMETER + VDI2770_DomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/DomainId + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_IdType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/IdType + + + + xs:string + Primary + + + PARAMETER + DocumentId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId + + + + xs:string + 3 608 870 A47_DE + + + PARAMETER + DocumentDomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentIdDomain/DocumentDomainId + + + + xs:string + + + + PARAMETER + VDI2770_Role + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Role + + + + xs:string + Responsible + + + PARAMETER + VDI2770_OrganisationId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationId + + + + xs:string + + + + PARAMETER + VDI2770_OrganisationName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationName + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_OrganisationOfficialName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationOfficialName + + + + xs:string + Bosch Rexroth AG + + + PARAMETER + VDI2770_Description + + + DE + Eine Beschreibung zur Dokumententeile ID. Da eine Sprachangabe nicht möglich ist, sollte die Sprache für dieses Metadatum vor der Lieferung abgestimmt werden. + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description + + + + xs:string + + + + PARAMETER + DocumentPartId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentPartId + + + + xs:string + + + + PARAMETER + DocumentClassification_ClassId + + + DE + eindeutige ID der Klasse in einer Klassifikation + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassId + + + + xs:string + 03-02 + + + PARAMETER + VDI2770_ClassName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassName + + + + xs:string + Bedienung + + + PARAMETER + ClassificationSystem + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassificationSystem + + + + xs:string + VDI2770:2018 + + + PARAMETER + DocumentVersionId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersionId + + + + xs:string + A47/05.2017 DE + + + PARAMETER + DocumentVersion_LanguageCode + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/LanguageCode + + + + xs:string + de + + + PARAMETER + VDI2770_Title + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Title + + + + xs:string + Rexroth Funk-Akkuschrauber Nexo + + + PARAMETER + VDI2770_Summary + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Summary + + + + xs:string + + + + PARAMETER + VDI2770_Keywords + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Keywords + + + + xs:string + + + + PARAMETER + VDI2770_StatusValue + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/StatusValue + + + + xs:string + Released + + + PARAMETER + VDI2770_SetDate + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/SetDate + + + + xs:string + + + + PARAMETER + VDI2770_Purpose + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Purpose + + + + xs:string + + + + PARAMETER + VDI2770_BasedOnProcedure + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/BasedOnProcedure + + + + xs:string + + + + PARAMETER + VDI2770_Comments + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Comments + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_Type + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ReferencedObjectType + + + + xs:string + Product + + + PARAMETER + VDI2770_ReferencedObject_RefType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/RefType + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_ObjectId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ObjectId + + + + xs:string + + + + PARAMETER + VDI2770_FileId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileId + + + + xs:string + + + + PARAMETER + VDI2770_FileName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileName + + + + xs:string + 3608870A47_AE_DE.pdf + + + PARAMETER + VDI2770_FileFormat + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileFormat + + + + xs:string + application/pdf + + + PARAMETER + File + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD005#008 + + + + /aasx/Document/3608870A47_AE_DE.pdf + application/pdf + + + + + PARAMETER + 3608870A47_ProjectPlanning_EN + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD001#001 + + + + + + PARAMETER + DocumentType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentType + + + + xs:string + Single + + + PARAMETER + VDI2770_DomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/DomainId + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_IdType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/IdType + + + + xs:string + Primary + + + PARAMETER + DocumentId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId + + + + xs:string + 3 608 870 A47_EN + + + PARAMETER + DocumentDomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentIdDomain/DocumentDomainId + + + + xs:string + + + + PARAMETER + VDI2770_Role + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Role + + + + xs:string + Responsible + + + PARAMETER + VDI2770_OrganisationId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationId + + + + xs:string + + + + PARAMETER + VDI2770_OrganisationName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationName + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_OrganisationOfficialName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationOfficialName + + + + xs:string + Bosch Rexroth AG + + + PARAMETER + VDI2770_Description + + + DE + Eine Beschreibung zur Dokumententeile ID. Da eine Sprachangabe nicht möglich ist, sollte die Sprache für dieses Metadatum vor der Lieferung abgestimmt werden. + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description + + + + xs:string + + + + PARAMETER + DocumentPartId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentPartId + + + + xs:string + + + + PARAMETER + DocumentClassification_ClassId + + + DE + eindeutige ID der Klasse in einer Klassifikation + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassId + + + + xs:string + 03-02 + + + PARAMETER + VDI2770_ClassName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassName + + + + xs:string + Operating + + + PARAMETER + ClassificationSystem + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassificationSystem + + + + xs:string + VDI2770:2018 + + + PARAMETER + DocumentVersionId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersionId + + + + xs:string + A47/05.2017 EN + + + PARAMETER + DocumentVersion_LanguageCode + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/LanguageCode + + + + xs:string + en + + + PARAMETER + VDI2770_Title + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Title + + + + xs:string + Nexo cordless Wi-Fi nutrunner by Rexroth + + + PARAMETER + VDI2770_Summary + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Summary + + + + xs:string + + + + PARAMETER + VDI2770_Keywords + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Keywords + + + + xs:string + + + + PARAMETER + VDI2770_StatusValue + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/StatusValue + + + + xs:string + Released + + + PARAMETER + VDI2770_SetDate + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/SetDate + + + + xs:string + + + + PARAMETER + VDI2770_Purpose + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Purpose + + + + xs:string + + + + PARAMETER + VDI2770_BasedOnProcedure + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/BasedOnProcedure + + + + xs:string + + + + PARAMETER + VDI2770_Comments + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Comments + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_Type + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ReferencedObjectType + + + + xs:string + Product + + + PARAMETER + VDI2770_ReferencedObject_RefType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/RefType + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_ObjectId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ObjectId + + + + xs:string + + + + PARAMETER + VDI2770_FileId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileId + + + + xs:string + + + + PARAMETER + VDI2770_FileName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileName + + + + xs:string + 3608870A47_AE_EN.pdf + + + PARAMETER + VDI2770_FileFormat + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileFormat + + + + xs:string + application/pdf + + + PARAMETER + File + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD005#008 + + + + /aasx/Document/3608870A47_AE_EN.pdf + application/pdf + + + + + PARAMETER + 36088702_SafetyInstructions_DE + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD001#001 + + + + + + PARAMETER + DocumentType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentType + + + + xs:string + Single + + + PARAMETER + VDI2770_DomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/DomainId + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_IdType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/IdType + + + + xs:string + Primary + + + PARAMETER + DocumentId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId + + + + xs:string + 3 608 870 2DE + + + PARAMETER + DocumentDomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentIdDomain/DocumentDomainId + + + + xs:string + + + + PARAMETER + VDI2770_Role + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Role + + + + xs:string + Responsible + + + PARAMETER + VDI2770_OrganisationId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationId + + + + xs:string + + + + PARAMETER + VDI2770_OrganisationName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationName + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_OrganisationOfficialName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationOfficialName + + + + xs:string + Bosch Rexroth AG + + + PARAMETER + VDI2770_Description + + + DE + Eine Beschreibung zur Dokumententeile ID. Da eine Sprachangabe nicht möglich ist, sollte die Sprache für dieses Metadatum vor der Lieferung abgestimmt werden. + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description + + + + xs:string + + + + PARAMETER + DocumentPartId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentPartId + + + + xs:string + + + + PARAMETER + DocumentClassification_ClassId + + + DE + eindeutige ID der Klasse in einer Klassifikation + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassId + + + + xs:string + 03-03 + + + PARAMETER + VDI2770_ClassName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassName + + + + xs:string + Allgemeine Sicherheit + + + PARAMETER + ClassificationSystem + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassificationSystem + + + + xs:string + VDI2770:2018 + + + PARAMETER + DocumentVersionId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersionId + + + + xs:string + AA/01.2010 DE + + + PARAMETER + DocumentVersion_LanguageCode + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/LanguageCode + + + + xs:string + de + + + PARAMETER + VDI2770_Title + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Title + + + + xs:string + Sicherheitshinweise für Rexroth Schraubsysteme + + + PARAMETER + VDI2770_Summary + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Summary + + + + xs:string + + + + PARAMETER + VDI2770_Keywords + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Keywords + + + + xs:string + + + + PARAMETER + VDI2770_StatusValue + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/StatusValue + + + + xs:string + Released + + + PARAMETER + VDI2770_SetDate + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/SetDate + + + + xs:string + + + + PARAMETER + VDI2770_Purpose + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Purpose + + + + xs:string + + + + PARAMETER + VDI2770_BasedOnProcedure + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/BasedOnProcedure + + + + xs:string + + + + PARAMETER + VDI2770_Comments + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Comments + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_Type + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ReferencedObjectType + + + + xs:string + Product + + + PARAMETER + VDI2770_ReferencedObject_RefType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/RefType + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_ObjectId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ObjectId + + + + xs:string + + + + PARAMETER + VDI2770_FileId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileId + + + + xs:string + + + + PARAMETER + VDI2770_FileName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileName + + + + xs:string + 36088702DE_AA.pdf + + + PARAMETER + VDI2770_FileFormat + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileFormat + + + + xs:string + application/pdf + + + PARAMETER + File + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD005#008 + + + + /aasx/Document/36088702DE_AA.pdf + application/pdf + + + + + PARAMETER + 36088702_SafetyInstructions_EN + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD001#001 + + + + + + PARAMETER + DocumentType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentType + + + + xs:string + Single + + + PARAMETER + VDI2770_DomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/DomainId + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_IdType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/IdType + + + + xs:string + Primary + + + PARAMETER + DocumentId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId + + + + xs:string + 3 608 870 2EN + + + PARAMETER + DocumentDomainId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentIdDomain/DocumentDomainId + + + + xs:string + + + + PARAMETER + VDI2770_Role + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Role + + + + xs:string + Responsible + + + PARAMETER + VDI2770_OrganisationId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationId + + + + xs:string + + + + PARAMETER + VDI2770_OrganisationName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationName + + + + xs:string + Bosch Rexroth + + + PARAMETER + VDI2770_OrganisationOfficialName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationOfficialName + + + + xs:string + Bosch Rexroth AG + + + PARAMETER + VDI2770_Description + + + DE + Eine Beschreibung zur Dokumententeile ID. Da eine Sprachangabe nicht möglich ist, sollte die Sprache für dieses Metadatum vor der Lieferung abgestimmt werden. + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description + + + + xs:string + + + + PARAMETER + DocumentPartId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentPartId + + + + xs:string + + + + PARAMETER + DocumentClassification_ClassId + + + DE + eindeutige ID der Klasse in einer Klassifikation + + + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassId + + + + xs:string + 03-03 + + + PARAMETER + VDI2770_ClassName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassName + + + + xs:string + General Safety + + + PARAMETER + ClassificationSystem + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassificationSystem + + + + xs:string + VDI2770:2018 + + + PARAMETER + DocumentVersionId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersionId + + + + xs:string + AA/01.2010 EN + + + PARAMETER + DocumentVersion_LanguageCode + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/LanguageCode + + + + xs:string + en + + + PARAMETER + VDI2770_Title + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Title + + + + xs:string + Safety Instructions for Rexroth Tightening Systems + + + PARAMETER + VDI2770_Summary + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Summary + + + + xs:string + + + + PARAMETER + VDI2770_Keywords + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Keywords + + + + xs:string + + + + PARAMETER + VDI2770_StatusValue + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/StatusValue + + + + xs:string + Released + + + PARAMETER + VDI2770_SetDate + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/SetDate + + + + xs:string + + + + PARAMETER + VDI2770_Purpose + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Purpose + + + + xs:string + + + + PARAMETER + VDI2770_BasedOnProcedure + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/BasedOnProcedure + + + + xs:string + + + + PARAMETER + VDI2770_Comments + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Comments + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_Type + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ReferencedObjectType + + + + xs:string + Product + + + PARAMETER + VDI2770_ReferencedObject_RefType + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/RefType + + + + xs:string + + + + PARAMETER + VDI2770_ReferencedObject_ObjectId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ObjectId + + + + xs:string + + + + PARAMETER + VDI2770_FileId + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileId + + + + xs:string + + + + PARAMETER + VDI2770_FileName + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileName + + + + xs:string + 36088702EN_AA.pdf + + + PARAMETER + VDI2770_FileFormat + + ExternalReference + + + ConceptDescription + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileFormat + + + + xs:string + application/pdf + + + PARAMETER + File + + ExternalReference + + + ConceptDescription + 0173-1#02-AAD005#008 + + + + /aasx/Document/36088702EN_AA.pdf + application/pdf + + + + + + + Service + http://boschrexroth.com/shells/0608842005/917004878/submodels/service + Instance + + ExternalReference + + + GlobalReference + https://www.hsu-hh.de/aut/aas/service + + + + + + PARAMETER + ContactInfo + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/contactinfo + + + + + + PARAMETER + NameOfSupplier + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO735#003 + + + + xs:string + Bosch Rexroth AG Service Tightening Systems + + + PARAMETER + ContactInfo_Role + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/role + + + + xs:string + Manufacturer + + + PARAMETER + PhysicalAddress + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/physicaladdress + + + + + + PARAMETER + CountryCode + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO730#001 + + + + xs:string + DE + + + PARAMETER + Street + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO128#001 + + + + xs:string + Bgm.-Dr.-Nebel-Str. 8 + + + PARAMETER + Zip + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO129#002 + + + + xs:string + 97816 + + + PARAMETER + CityTown + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO132#001 + + + + xs:string + Lohr + + + PARAMETER + StateCounty + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO133#002 + + + + xs:string + Bayern + + + + + PARAMETER + Email + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/email + + + + xs:string + service@boschrexroth.de + + + PARAMETER + URL + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO694#001 + + + + xs:anyURI + https://www.boschrexroth.com/de/de/service/startseite-service + + + PARAMETER + PhoneNumber + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO136#002 + + + + xs:string + +499352405060 + + + PARAMETER + Fax + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/fax + + + + xs:string + + + + + + + + Identification + http://boschrexroth.com/shells/0608842005/917004878/submodels/identification + Instance + + ExternalReference + + + GlobalReference + https://www.hsu-hh.de/aut/aas/identification + + + + + + PARAMETER + ManufacturerName + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO677#002 + + + + xs:string + Bosch Rexroth AG + + + PARAMETER + GLNOfManufacturer + + ExternalReference + + + ConceptDescription + 0173-1#02-AAY812#001 + + + + xs:string + 4048118000006 + + + SupplierOfTheIdentifier + + ExternalReference + + + ConceptDescription + 0173-1#02-AAP796#004 + + + + xs:string + GLN GS1 + + + MAN_PROD_NUM + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO676#003 + + + + xs:string + 0608842005 + + + PARAMETER + ManufacturerProductDesignation + + ExternalReference + + + ConceptDescription + 0173-1#02-AAW338#001 + + + + xs:string + NXP012QD-36V + + + PARAMETER + ManufacturerProductDescription + + ExternalReference + + + ConceptDescription + 0173-1#02-AAU734#001 + + + + xs:string + The most intelligent hand-held nutrunner in the world + + + PARAMETER + ManufacturerProductFamily + + ExternalReference + + + ConceptDescription + 0173-1#02-AAU731#001 + + + + xs:string + Cordless Nutrunner + + + PARAMETER + ClassificationSystem + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO715#002 + + + + xs:string + eCl@ss + + + SecondaryKeyTyp + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/secondarykeytyp + + + + xs:string + + + + PARAMETER + TypThumbnail + + ExternalReference + + + ConceptDescription + www.company.com/ids/cd/4401_1272_7091_3437 + + + + /ST_Mittelgriffschrauber_NXP_Produktbild_V01_20160822_161306.jpg + image/jpeg + + + PARAMETER + AssetId + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/assetid + + + + xs:string + http://dc-qr.com?m=0608842005&s=917004878 + + + PARAMETER + SerialNumber + + ExternalReference + + + ConceptDescription + 0173-1#02-AAM556#002 + + + + xs:string + 917004878 + + + PARAMETER + BatchNumber + + ExternalReference + + + ConceptDescription + 0173-1#02-AAQ196#001 + + + + xs:string + + + + PARAMETER + SecondaryKeyInstance + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/secondarykeyinstance + + + + xs:string + + + + PARAMETER + DateOfManufacture + + ExternalReference + + + ConceptDescription + 0173-1#02-AAR972#002 + + + + xs:string + 2019 + + + PARAMETER + DeviceRevision + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/devicerevision + + + + xs:string + + + + PARAMETER + SoftwareRevision + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/softwarerevision + + + + xs:string + + + + PARAMETER + HardwareRevision + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/hardwarerevision + + + + xs:string + + + + PARAMETER + ContactInfo + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/contactinfo + + + + + + PARAMETER + NameOfSupplier + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO735#003 + + + + xs:string + Bosch Rexroth AG + + + PARAMETER + ContactInfo_Role + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/role + + + + xs:string + Manufacturer + + + PARAMETER + PhysicalAddress + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/physicaladdress + + + + + + PARAMETER + CountryCode + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO730#001 + + + + xs:string + DE + + + PARAMETER + Street + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO128#001 + + + + xs:string + Zum Eisengießer 1 + + + PARAMETER + Zip + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO129#002 + + + + xs:string + 97816 + + + PARAMETER + CityTown + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO132#001 + + + + xs:string + Lohr am Main + + + PARAMETER + StateCounty + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO133#002 + + + + xs:string + Bayern + + + + + PARAMETER + Email + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/email + + + + xs:string + service@boschrexroth.de + + + PARAMETER + URL + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO694#001 + + + + xs:anyURI + https://www.boschrexroth.com/de/de/home/index + + + PARAMETER + PhoneNumber + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO136#002 + + + + xs:string + +499352405060 + + + PARAMETER + Fax + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/fax + + + + xs:string + + + + + + PARAMETER + CompanyLogo + + ExternalReference + + + ConceptDescription + https://www.hsu-hh.de/aut/aas/companylogo + + + + /aasx/Identification/logo.png + image/png + + + PARAMETER + URL + + ExternalReference + + + ConceptDescription + 0173-1#02-AAO694#001 + + + + xs:anyURI + http://dc-qr.com?m=0608842005&s=917004878 + + + + + + + ManufacturerName + 0173-1#02-AAO677#002 + + + ManufacturerProductDesignation + 0173-1#02-AAW338#001 + + + PhysicalAddress + https://www.hsu-hh.de/aut/aas/physicaladdress + + + CountryCode + + + + + 0173-1#02-AAO730#001 + + + Street + 0173-1#02-AAO128#001 + + + Zip + 0173-1#02-AAO129#002 + + + CityTown + 0173-1#02-AAO132#001 + + + StateCounty + 0173-1#02-AAO133#002 + + + ManufacturerProductFamily + 0173-1#02-AAU731#001 + + + SerialNumber + 0173-1#02-AAM556#002 + + + BatchNumber + 0173-1#02-AAQ196#001 + + + ProductCountryOfOrigin + 0173-1#02-AAO841#001 + + + YearOfConstruction + 0173-1#02-AAP906#001 + + + ProductMarking + https://www.hsu-hh.de/aut/aas/productmarking + + + CEQualificationPresent + 0173-1#02-BAF053#008 + + + File + 0173-1#02-AAD005#008 + + + DocumentationItem + 0173-1#02-AAD001#001 + + + DocumentType + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentType + + + VDI2770_DomainId + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/DomainId + + + VDI2770_IdType + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId/IdType + + + DocumentId + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentId + + + DocumentDomainId + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentIdDomain/DocumentDomainId + + + VDI2770_Role + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Role + + + VDI2770_OrganisationId + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationId + + + VDI2770_OrganisationName + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationName + + + VDI2770_OrganisationOfficialName + http://www.vdi.de/2770/AssetDocumentation/Document/Party/Organisation/OrganisationOfficialName + + + VDI2770_Description + http://www.vdi.de/2770/AssetDocumentation/Document/Description + + + DocumentPartId + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentPartId + + + DocumentClassification_ClassId + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassId + + + VDI2770_ClassName + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassName + + + VDI2770_ClassificationSystem + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentClassification/ClassificationSystem + + + DocumentVersionId + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersionId + + + DocumentVersion_LanguageCode + http://www.vdi.de/2770/AssetDocumentation/Document/LanguageCode + + + VDI2770_Title + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Title + + + VDI2770_Summary + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Summary + + + VDI2770_Keywords + http://www.vdi.de/2770/AssetDocumentation/Document/Description/Keywords + + + VDI2770_StatusValue + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/StatusValue + + + VDI2770_SetDate + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/SetDate + + + VDI2770_Purpose + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Purpose + + + VDI2770_BasedOnProcedure + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/BasedOnProcedure + + + VDI2770_Comments + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/LifeCycleStatus/Comments + + + VDI2770_ReferencedObject_Type + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ReferencedObjectType + + + VDI2770_ReferencedObject_RefType + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/RefType + + + VDI2770_ReferencedObject_ObjectId + http://www.vdi.de/2770/AssetDocumentation/Document/ReferencedObject/ObjectId + + + VDI2770_FileId + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileId + + + VDI2770_FileName + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileName + + + VDI2770_FileFormat + http://www.vdi.de/2770/AssetDocumentation/Document/DocumentVersion/StoredDocumentRepresentation/DigitalFile/FileFormat + + + OrganisationContactInfo + https://www.hsu-hh.de/aut/aas/contactinfo + + + NameOfSupplier + + + + + 0173-1#02-AAO735#003 + + + ContactInfo_Role + https://www.hsu-hh.de/aut/aas/role + + + Email + https://www.hsu-hh.de/aut/aas/email + + + URL + 0173-1#02-AAO694#001 + + + PhoneNumber + 0173-1#02-AAO136#002 + + + Fax + https://www.hsu-hh.de/aut/aas/fax + + + ModelReference + + + ConceptDescription + 0173-1#02-AAO136#002 + + + + + + + GLNOfManufacturer + 0173-1#02-AAY812#001 + + + SupplierOfTheIdentifier + 0173-1#02-AAP796#004 + + + MAN_PROD_NUM + + + EN + product article number of manufacturer + + + 0173-1#02-AAO676#003 + + + ManufacturerProductDescription + 0173-1#02-AAU734#001 + + + GLNOfSupplier + 0173-1#02-AAY813#001 + + + SupplierIdProvider + https://www.hsu-hh.de/aut/aas/supplieridprovider + + + SUP_PROD_NUM + 0173-1#02-AAO736#004 + + + SupplierProductDesignation + 0173-1#02-AAM551#002 + + + SupplierProductDescription + 0173-1#02-AAU730#001 + + + ClassificationSystem + 0173-1#02-AAO715#002 + + + SecondaryKeyTyp + https://www.hsu-hh.de/aut/aas/secondarykeytyp + + + TypThumbnail + www.company.com/ids/cd/4401_1272_7091_3437 + + + AssetId + https://www.hsu-hh.de/aut/aas/assetid + + + SecondaryKeyInstance + https://www.hsu-hh.de/aut/aas/secondarykeyinstance + + + DateOfManufacture + 0173-1#02-AAR972#002 + + + DeviceRevision + https://www.hsu-hh.de/aut/aas/devicerevision + + + SoftwareRevision + https://www.hsu-hh.de/aut/aas/softwarerevision + + + HardwareRevision + https://www.hsu-hh.de/aut/aas/hardwarerevision + + + QrCode + https://www.hsu-hh.de/aut/aas/qrcode + + + CompanyLogo + https://www.hsu-hh.de/aut/aas/companylogo + + + \ No newline at end of file diff --git a/projects/aas-server/src/test/assets/test-db.json b/projects/aas-server/src/test/assets/test-db.json index 27d09c66..e31c7351 100644 --- a/projects/aas-server/src/test/assets/test-db.json +++ b/projects/aas-server/src/test/assets/test-db.json @@ -2,7 +2,7 @@ "documents": [ { "id": "AssetAdministrationShell---0EEEF851", - "endpoint": "AASXServer", + "endpoint": "AASServer", "address": "AssetAdministrationShell---0EEEF851", "idShort": "TestAASV2", "readonly": false, @@ -14,7 +14,7 @@ }, { "id": "http://customer.com/aas/9175_7013_7091_9168", - "endpoint": "AASXServer", + "endpoint": "AASServer", "address": "http://customer.com/aas/9175_7013_7091_9168", "idShort": "ExampleMotor", "readonly": false, @@ -433,14 +433,14 @@ { "url": "file:///samples", "name": "Samples", - "type": "AasxDirectory", - "version": "3.0" + "type": "FileSystem", + "version": "v3" }, { "url": "http://localhost:5001/", - "name": "AASXServer", - "type": "AasxServer", - "version": "3.0" + "name": "AASServer", + "type": "AASServer", + "version": "v3" } ], "elements": [ diff --git a/projects/aas-server/src/test/auth/locale-user-storage.spec.ts b/projects/aas-server/src/test/auth/local-user-storage.spec.ts similarity index 92% rename from projects/aas-server/src/test/auth/locale-user-storage.spec.ts rename to projects/aas-server/src/test/auth/local-user-storage.spec.ts index 3be082cc..c6359679 100644 --- a/projects/aas-server/src/test/auth/locale-user-storage.spec.ts +++ b/projects/aas-server/src/test/auth/local-user-storage.spec.ts @@ -10,13 +10,14 @@ import 'reflect-metadata'; import { describe, afterEach, beforeEach, it, expect, jest } from '@jest/globals'; import fs from 'fs'; import os from 'os'; -import path from 'path'; +import path from 'path/posix'; import { Cookie } from 'common'; import { UserStorage } from '../../app/auth/user-storage.js'; -import { LocaleUserStorage } from '../../app/auth/locale-user-storage.js'; +import { LocalUserStorage } from '../../app/auth/local-user-storage.js'; import { UserData } from '../../app/auth/user-data.js'; import { createSpyObj } from '../utils.js'; import { Logger } from '../../app/logging/logger.js'; +import { slash } from '../../app/convert.js'; describe('LocaleUserStorage', function () { let userStorage: UserStorage; @@ -34,7 +35,7 @@ describe('LocaleUserStorage', function () { lastLoggedIn: new Date(0), }; - userStorage = new LocaleUserStorage(createSpyObj(['error']), os.tmpdir()); + userStorage = new LocalUserStorage(createSpyObj(['error']), os.tmpdir()); }); afterEach(() => { @@ -42,19 +43,19 @@ describe('LocaleUserStorage', function () { }); describe('existsSync', function () { - it('indicates that john.doe@email.com exists', async function () { + it('indicates that john.doe@email.com exists', async () => { jest.spyOn(fs, 'existsSync').mockImplementation(() => true); await expect(userStorage.existAsync('john.doe@email.com')).resolves.toBe(true); }); - it('indicates that unknown@email.com does not exist', async function () { + it('indicates that unknown@email.com does not exist', async () => { jest.spyOn(fs, 'existsSync').mockImplementation(() => false); await expect(userStorage.existAsync('unknown@email.com')).resolves.toBe(false); }); }); describe('writeAsync', function () { - it('writes a new user', async function () { + it('writes a new user', async () => { jest.spyOn(fs, 'existsSync').mockReturnValue(false); jest.spyOn(fs.promises, 'mkdir').mockResolvedValue(undefined); jest.spyOn(fs.promises, 'writeFile').mockResolvedValue(); @@ -73,27 +74,27 @@ describe('LocaleUserStorage', function () { }); describe('readAsync', function () { - it('reads the data of john.doe@email.com', async function () { + it('reads the data of john.doe@email.com', async () => { jest.spyOn(fs, 'existsSync').mockReturnValue(true); jest.spyOn(fs.promises, 'readFile').mockResolvedValue(Buffer.from(JSON.stringify(johnDoe))); await expect(userStorage.readAsync('john.doe@email.com')).resolves.toEqual(johnDoe); }); - it('reads "undefined" for an unknown user', async function () { + it('reads "undefined" for an unknown user', async () => { jest.spyOn(fs, 'existsSync').mockReturnValue(false); await expect(userStorage.readAsync('unknown@email.com')).resolves.toBeUndefined(); }); }); describe('deleteAsync', function () { - it('john.doe@email.com', async function () { + it('john.doe@email.com', async () => { jest.spyOn(fs, 'existsSync').mockReturnValue(true); jest.spyOn(fs.promises, 'rm').mockImplementation(() => new Promise(resolve => resolve())); await expect(userStorage.deleteAsync('john.doe@email.com')).resolves.toBe(true); expect(fs.promises.rm).toHaveBeenCalled(); }); - it('indicates that an unknown user was not deleted', async function () { + it('indicates that an unknown user was not deleted', async () => { jest.spyOn(fs, 'existsSync').mockReturnValue(false); await expect(userStorage.deleteAsync('unknown@email.com')).resolves.toBe(false); }); @@ -109,7 +110,7 @@ describe('LocaleUserStorage', function () { }); beforeEach(async () => { - usersDir = os.tmpdir(); + usersDir = slash(os.tmpdir()); cookies = Buffer.from( JSON.stringify([ { @@ -123,10 +124,10 @@ describe('LocaleUserStorage', function () { ]), ); - userStorage = new LocaleUserStorage(createSpyObj(['error']), usersDir); + userStorage = new LocalUserStorage(createSpyObj(['error']), usersDir); }); - describe('checkAsync', () => { + describe('checkCookieAsync', () => { it('indicates that "Cookie1" for john.doe@email.com exist', async () => { jest.spyOn(fs, 'existsSync').mockReturnValue(true); jest.spyOn(fs.promises, 'readFile').mockResolvedValue(Buffer.from(cookies)); @@ -145,7 +146,7 @@ describe('LocaleUserStorage', function () { }); }); - describe('getAsync', () => { + describe('getCookieAsync', () => { beforeEach(() => { jest.spyOn(fs, 'existsSync').mockReturnValue(true); jest.spyOn(fs.promises, 'readFile').mockResolvedValue(Buffer.from(cookies)); @@ -167,7 +168,7 @@ describe('LocaleUserStorage', function () { }); }); - describe('getAllAsync', () => { + describe('getCookiesAsync', () => { beforeEach(() => { jest.spyOn(fs, 'existsSync').mockReturnValue(true); jest.spyOn(fs.promises, 'readFile').mockResolvedValue(Buffer.from(cookies)); @@ -187,7 +188,7 @@ describe('LocaleUserStorage', function () { }); }); - describe('setAsync', () => { + describe('setCookieAsync', () => { beforeEach(() => { jest.spyOn(fs, 'existsSync').mockReturnValue(true); jest.spyOn(fs.promises, 'readFile').mockResolvedValue(Buffer.from(cookies)); diff --git a/projects/aas-server/src/test/configuration.spec.ts b/projects/aas-server/src/test/configuration.spec.ts index 1481fb85..db1199cd 100644 --- a/projects/aas-server/src/test/configuration.spec.ts +++ b/projects/aas-server/src/test/configuration.spec.ts @@ -6,51 +6,73 @@ * *****************************************************************************/ -import { getEndpointName, getEndpointType } from '../app/configuration.js'; import { describe, it, expect } from '@jest/globals'; +import { AASEndpoint } from 'common'; +import { urlToEndpoint } from '../app/configuration.js'; describe('configuration', function () { - describe('getEndpointName', function () { - it('gets the endpoint name from an URL string', function () { - expect(getEndpointName('http://localhost:1234/?name=Test')).toEqual('Test'); + describe('urlToEndpoint', function () { + it('gets an endpoint from an URL string', function () { + expect(urlToEndpoint('http://localhost:1234/?name=Test')).toEqual({ + name: 'Test', + url: 'http://localhost:1234/', + type: 'AASServer', + version: 'v3', + } as AASEndpoint); }); - it('gets the endpoint name from a URL', function () { - expect(getEndpointName(new URL('http://localhost:1234/?name=Test'))).toEqual('Test'); - }); - - it('gets the default name of an AASX server', function () { - expect(getEndpointName('http://localhost:1234/')).toEqual('http://localhost:1234/'); + it('gets an endpoint from an URL', function () { + expect(urlToEndpoint(new URL('http://localhost:1234/?name=Test'))).toEqual({ + name: 'Test', + url: 'http://localhost:1234/', + type: 'AASServer', + version: 'v3', + } as AASEndpoint); }); - it('gets the default name of an AAS registry', function () { - expect(getEndpointName('http://localhost:1234/v1/registry?type=AASRegistry')).toEqual( - 'http://localhost:1234/v1/registry', - ); - }); - - it('gets the default name of an OPCUA server', function () { - expect(getEndpointName(new URL('opc.tcp://172.16.160.178:30001/I4AASServer'))).toEqual( - 'opc.tcp://172.16.160.178:30001/I4AASServer', - ); + it('gets the endpoint name from a URL', function () { + expect(urlToEndpoint('http://localhost:1234/?name=Test&version=v2')).toEqual({ + name: 'Test', + url: 'http://localhost:1234/', + type: 'AASServer', + version: 'v2', + } as AASEndpoint); }); - it('gets the default name of an AASX directory', function () { - expect(getEndpointName('file:///samples')).toEqual('file:///samples'); + it('gets an endpoint of an AASX server', function () { + expect(urlToEndpoint('http://localhost:1234/')).toEqual({ + name: 'http://localhost:1234/', + url: 'http://localhost:1234/', + type: 'AASServer', + version: 'v3', + } as AASEndpoint); }); - }); - describe('getEndpointType', function () { - it('gets the endpoint type from an URL string', function () { - expect(getEndpointType('http://localhost:1234/?type=AasxDirectory')).toEqual('AasxDirectory'); + it('gets an endpoint of an WebDAV server', function () { + expect(urlToEndpoint('http://localhost:1234/endpoints/samples')).toEqual({ + name: 'samples', + url: 'http://localhost:1234/endpoints/samples', + type: 'WebDAV', + version: 'v3', + } as AASEndpoint); }); - it('gets the endpoint type from a URL', function () { - expect(getEndpointType(new URL('http://localhost:1234/?type=OpcuaServer'))).toEqual('OpcuaServer'); + it('gets an endpoint of an OPCUA server', function () { + expect(urlToEndpoint(new URL('opc.tcp://172.16.160.178:30001/I4AASServer?version=v1'))).toEqual({ + name: 'I4AASServer', + url: 'opc.tcp://172.16.160.178:30001/I4AASServer', + type: 'OpcuaServer', + version: 'v1', + } as AASEndpoint); }); - it('gets "AasxServer" as default', function () { - expect(getEndpointType('http://localhost:1234/')).toEqual('AasxServer'); + it('gets an endpoint of an local directory', function () { + expect(urlToEndpoint('file:///endpoints/samples')).toEqual({ + name: 'samples', + url: 'file:///endpoints/samples', + type: 'FileSystem', + version: 'v3', + } as AASEndpoint); }); }); }); diff --git a/projects/aas-server/src/test/controller/containers-controller.spec.ts b/projects/aas-server/src/test/controller/containers-controller.spec.ts index 216432a4..a704cd92 100644 --- a/projects/aas-server/src/test/controller/containers-controller.spec.ts +++ b/projects/aas-server/src/test/controller/containers-controller.spec.ts @@ -13,7 +13,7 @@ import express, { Express, json, urlencoded } from 'express'; import morgan from 'morgan'; import request from 'supertest'; import { Readable } from 'stream'; -import { resolve } from 'path'; +import { resolve } from 'path/posix'; import { aas } from 'common'; import { sampleDocument } from '../assets/sample-document.js'; diff --git a/projects/aas-server/src/test/controller/endpoints-controller.spec.ts b/projects/aas-server/src/test/controller/endpoints-controller.spec.ts index ad09f147..c34e52bb 100644 --- a/projects/aas-server/src/test/controller/endpoints-controller.spec.ts +++ b/projects/aas-server/src/test/controller/endpoints-controller.spec.ts @@ -74,7 +74,7 @@ describe('EndpointsController', function () { const endpoints: AASEndpoint = { name: 'Test', url: 'http://localhost:1234', - type: 'AasxServer', + type: 'AASServer', }; aasProvider.getEndpoints.mockResolvedValue([endpoints]); @@ -86,7 +86,7 @@ describe('EndpointsController', function () { }); it('POST: /api/v1/endpoints/:name', async function () { - const endpoint: AASEndpoint = { name: 'Samples', url: 'file:///assets/samples', type: 'AasxDirectory' }; + const endpoint: AASEndpoint = { name: 'Samples', url: 'file:///assets/samples', type: 'FileSystem' }; aasProvider.addEndpointAsync.mockResolvedValue(); auth.hasUserAsync.mockResolvedValue(true); const response = await request(app) diff --git a/projects/aas-server/src/test/controller/templates-controller.spec.ts b/projects/aas-server/src/test/controller/templates-controller.spec.ts index 440b242a..fba635b7 100644 --- a/projects/aas-server/src/test/controller/templates-controller.spec.ts +++ b/projects/aas-server/src/test/controller/templates-controller.spec.ts @@ -66,7 +66,7 @@ describe('TemplateController', () => { it('getTemplates: /api/v1/templates', async () => { const templates: TemplateDescriptor[] = [ - { idShort: 'TestTemplate', id: 'http://localhost:1234/a/b/c', template: null, modelType: 'Submodel' }, + { idShort: 'TestTemplate', id: 'http://localhost:1234/a/b/c', modelType: 'Submodel' }, ]; templateStorage.readTemplatesAsync.mockResolvedValue(templates); diff --git a/projects/aas-server/src/test/file-storage/local-file-storage.spec.ts b/projects/aas-server/src/test/file-storage/local-file-storage.spec.ts index 0717a8ee..d51627db 100644 --- a/projects/aas-server/src/test/file-storage/local-file-storage.spec.ts +++ b/projects/aas-server/src/test/file-storage/local-file-storage.spec.ts @@ -10,7 +10,7 @@ import 'reflect-metadata'; import fs, { Dirent } from 'fs'; import { describe, beforeEach, it, expect, jest, afterEach } from '@jest/globals'; import { LocalFileStorage } from '../../app/file-storage/local-file-storage.js'; -import { resolve, sep } from 'path'; +import { resolve, sep } from 'path/posix'; import { createSpyObj } from '../utils.js'; describe('LocalFileStorage', () => { diff --git a/projects/aas-server/src/test/live/http/http-subscription.spec.ts b/projects/aas-server/src/test/live/http/http-subscription.spec.ts index acd02f8b..fd4474b1 100644 --- a/projects/aas-server/src/test/live/http/http-subscription.spec.ts +++ b/projects/aas-server/src/test/live/http/http-subscription.spec.ts @@ -11,12 +11,12 @@ import { aas, DefaultType, LiveRequest } from 'common'; import { Logger } from '../../../app/logging/logger.js'; import { HttpSubscription } from '../../../app/live/http/http-subscription.js'; import { SocketClient } from '../../../app/live/socket-client.js'; -import { AasxServer } from '../../../app/packages/aasx-server/aasx-server.js'; +import { AASServer } from '../../../app/packages/aas-server/aas-server.js'; import env from '../../assets/aas-environment.js'; import { createSpyObj, DoneFn } from '../../utils.js'; describe('HttpSubscription', function () { - let aasxServer: jest.Mocked; + let aasxServer: jest.Mocked; let logger: jest.Mocked; let client: jest.Mocked; let subscription: HttpSubscription; @@ -24,7 +24,7 @@ describe('HttpSubscription', function () { beforeEach(function () { logger = createSpyObj(['error', 'warning', 'info', 'debug', 'start', 'stop']); client = createSpyObj(['has', 'subscribe', 'notify']); - aasxServer = createSpyObj([ + aasxServer = createSpyObj([ 'getShellsAsync', 'commitAsync', 'openFileAsync', @@ -47,7 +47,7 @@ describe('HttpSubscription', function () { }; const request: LiveRequest = { - endpoint: 'AasxDirectory', + endpoint: 'FileSystem', id: 'http://customer.com/aas/9175_7013_7091_9168', nodes: [ { diff --git a/projects/aas-server/src/test/packages/aasx-server/aasx-server-package.spec.ts b/projects/aas-server/src/test/packages/aas-server/aas-server-package.spec.ts similarity index 78% rename from projects/aas-server/src/test/packages/aasx-server/aasx-server-package.spec.ts rename to projects/aas-server/src/test/packages/aas-server/aas-server-package.spec.ts index 216afa1c..8618e1b2 100644 --- a/projects/aas-server/src/test/packages/aasx-server/aasx-server-package.spec.ts +++ b/projects/aas-server/src/test/packages/aas-server/aas-server-package.spec.ts @@ -9,24 +9,24 @@ import { describe, beforeEach, it, expect, jest } from '@jest/globals'; import { aas } from 'common'; import { Logger } from '../../../app/logging/logger.js'; -import { AasxServer } from '../../../app/packages/aasx-server/aasx-server.js'; -import { AasxServerPackage } from '../../../app/packages/aasx-server/aasx-server-package.js'; +import { AASServer } from '../../../app/packages/aas-server/aas-server.js'; +import { AASServerPackage } from '../../../app/packages/aas-server/aas-server-package.js'; import { createSpyObj } from '../../utils.js'; describe('AasxServerPackage', () => { - let aasPackage: AasxServerPackage; + let aasPackage: AASServerPackage; let logger: jest.Mocked; - let server: jest.Mocked; + let server: jest.Mocked; let env: aas.Environment; beforeEach(() => { logger = createSpyObj(['error', 'warning', 'info', 'debug', 'start', 'stop']); - server = createSpyObj(['readEnvironmentAsync'], { + server = createSpyObj(['readEnvironmentAsync'], { url: 'http:/localhost:1234', name: 'Test', }); - aasPackage = new AasxServerPackage(logger, server, 'CunaCup_Becher1'); + aasPackage = new AASServerPackage(logger, server, 'CunaCup_Becher1'); env = { assetAdministrationShells: [ { diff --git a/projects/aas-server/src/test/packages/aasx-server/aasx-server-v0.spec.ts b/projects/aas-server/src/test/packages/aas-server/aas-server-v0.spec.ts similarity index 94% rename from projects/aas-server/src/test/packages/aasx-server/aasx-server-v0.spec.ts rename to projects/aas-server/src/test/packages/aas-server/aas-server-v0.spec.ts index b2e0e65b..324015f6 100644 --- a/projects/aas-server/src/test/packages/aasx-server/aasx-server-v0.spec.ts +++ b/projects/aas-server/src/test/packages/aas-server/aas-server-v0.spec.ts @@ -10,25 +10,25 @@ import { describe, beforeEach, it, expect, jest, afterEach } from '@jest/globals import http, { IncomingMessage } from 'http'; import { Socket } from 'net'; import { aas, selectElement } from 'common'; -import { AasxServer } from '../../../app/packages/aasx-server/aasx-server.js'; +import { AASServer } from '../../../app/packages/aas-server/aas-server.js'; import listaas from '../../assets/test-aas/listaas.js'; import becher1 from '../../assets/test-aas/cuna-cup-becher1.js'; import submodels from '../../assets/test-aas/submodels.js'; import nameplate from '../../assets/test-aas/nameplate-becher1.js'; import digitalProductPassport from '../../assets/test-aas/digital-product-passport-becher1.js'; import customerFeedback from '../../assets/test-aas/customer-feedback-becher1.js'; -import { AasxServerV0 } from '../../../app/packages/aasx-server/aasx-server-v0.js'; +import { AASServerV0 } from '../../../app/packages/aas-server/aas-server-v0.js'; import { Logger } from '../../../app/logging/logger.js'; import aasEnvironment from '../../assets/aas-environment.js'; import { createSpyObj } from '../../utils.js'; -describe('AasxServerV0', function () { +describe('AASServerV0', function () { let logger: jest.Mocked; - let server: AasxServer; + let server: AASServer; beforeEach(function () { logger = createSpyObj(['error', 'warning', 'info', 'debug', 'start', 'stop']); - server = new AasxServerV0(logger, 'http://localhost:1234', 'AASX Server'); + server = new AASServerV0(logger, 'http://localhost:1234', 'AASX Server'); }); afterEach(() => { diff --git a/projects/aas-server/src/test/packages/aasx-server/aasx-server-v3.spec.ts b/projects/aas-server/src/test/packages/aas-server/aas-server-v3.spec.ts similarity index 97% rename from projects/aas-server/src/test/packages/aasx-server/aasx-server-v3.spec.ts rename to projects/aas-server/src/test/packages/aas-server/aas-server-v3.spec.ts index bacde40a..eabb98f8 100644 --- a/projects/aas-server/src/test/packages/aasx-server/aasx-server-v3.spec.ts +++ b/projects/aas-server/src/test/packages/aas-server/aas-server-v3.spec.ts @@ -8,7 +8,7 @@ import http, { IncomingMessage } from 'http'; import env from '../../assets/aas-environment.js'; -import { AasxServerV3, OperationResult } from '../../../app/packages/aasx-server/aasx-server-v3.js'; +import { AASServerV3, OperationResult } from '../../../app/packages/aas-server/aas-server-v3.js'; import { aas, DifferenceItem } from 'common'; import { cloneDeep } from 'lodash-es'; import { Socket } from 'net'; @@ -16,13 +16,13 @@ import { Logger } from '../../../app/logging/logger.js'; import { createSpyObj } from '../../utils.js'; import { describe, beforeEach, it, expect, jest, afterEach } from '@jest/globals'; -describe('AasxServerV3', function () { +describe('AASServerV3', function () { let logger: Logger; - let server: AasxServerV3; + let server: AASServerV3; beforeEach(function () { logger = createSpyObj(['error', 'warning', 'info', 'debug', 'start', 'stop']); - server = new AasxServerV3(logger, 'http://localhost:1234', 'AASX Server'); + server = new AASServerV3(logger, 'http://localhost:1234', 'AASX Server'); }); describe('resolveNodeId', function () { diff --git a/projects/aas-server/src/test/packages/aasx-server/aasx-package.spec.ts b/projects/aas-server/src/test/packages/aas-server/aasx-package.spec.ts similarity index 90% rename from projects/aas-server/src/test/packages/aasx-server/aasx-package.spec.ts rename to projects/aas-server/src/test/packages/aas-server/aasx-package.spec.ts index 6a5cac34..d85bdbd3 100644 --- a/projects/aas-server/src/test/packages/aasx-server/aasx-package.spec.ts +++ b/projects/aas-server/src/test/packages/aas-server/aasx-package.spec.ts @@ -7,13 +7,12 @@ *****************************************************************************/ import { describe, beforeEach, it, expect, jest } from '@jest/globals'; -import { AasxPackage } from '../../../app/packages/aasx-directory/aasx-package.js'; -import { AasxDirectory } from '../../../app/packages/aasx-directory/aasx-directory.js'; +import { AasxPackage } from '../../../app/packages/file-system/aasx-package.js'; +import { AasxDirectory } from '../../../app/packages/file-system/aasx-directory.js'; import { Logger } from '../../../app/logging/logger.js'; import { LocalFileStorage } from '../../../app/file-storage/local-file-storage.js'; import { createSpyObj } from '../../utils.js'; import { FileStorage } from '../../../app/file-storage/file-storage.js'; -import { resolve } from 'path'; describe('AasxPackage', function () { let logger: jest.Mocked; diff --git a/projects/aas-server/src/test/packages/json-reader.spec.ts b/projects/aas-server/src/test/packages/json-reader.spec.ts index 9658c43c..fd3fc7ab 100644 --- a/projects/aas-server/src/test/packages/json-reader.spec.ts +++ b/projects/aas-server/src/test/packages/json-reader.spec.ts @@ -9,7 +9,7 @@ import { Logger } from '../../app/logging/logger.js'; import { readFile } from 'fs/promises'; import { JsonReader } from '../../app/packages/json-reader.js'; -import { resolve } from 'path'; +import { resolve } from 'path/posix'; import { createSpyObj } from '../utils.js'; import { describe, beforeEach, it, expect, jest } from '@jest/globals'; diff --git a/projects/aas-server/src/test/packages/xml-reader-v1.spec.ts b/projects/aas-server/src/test/packages/xml-reader-v1.spec.ts new file mode 100644 index 00000000..f1c8a784 --- /dev/null +++ b/projects/aas-server/src/test/packages/xml-reader-v1.spec.ts @@ -0,0 +1,68 @@ +/****************************************************************************** + * + * Copyright (c) 2019-2024 Fraunhofer IOSB-INA Lemgo, + * eine rechtlich nicht selbstaendige Einrichtung der Fraunhofer-Gesellschaft + * zur Foerderung der angewandten Forschung e.V. + * + *****************************************************************************/ + +import { readFile } from 'fs/promises'; +import { resolve } from 'path/posix'; +import { Logger } from '../../app/logging/logger.js'; +import { XmlReaderV1 } from '../../app/packages/xml-reader-v1.js'; +import { createSpyObj } from '../utils.js'; +import { describe, beforeAll, beforeEach, it, expect, jest } from '@jest/globals'; + +describe('XmlReader', function () { + describe('with default namespace v2.0', function () { + let reader: XmlReaderV1; + let logger: jest.Mocked; + let xml: string; + let path: string; + + beforeAll(async function () { + path = resolve('./src/test/assets/aas-default-namespace.xml'); + xml = (await readFile(path)).toString(); + }); + + beforeEach(function () { + logger = createSpyObj(['error', 'warning', 'info', 'debug', 'start', 'stop']); + reader = new XmlReaderV1(logger, xml); + }); + + it('should be created', function () { + expect(reader).toBeTruthy(); + }); + + it('reads the AAS environment from a xml source', function () { + const environment = reader.readEnvironment(); + expect(environment).toBeDefined(); + }); + }); + + describe('with prefix namespace v1.0', function () { + let reader: XmlReaderV1; + let logger: jest.Mocked; + let xml: string; + let path: string; + + beforeAll(async function () { + path = resolve('./src/test/assets/aas-prefix-namespace.xml'); + xml = (await readFile(path)).toString(); + }); + + beforeEach(function () { + logger = createSpyObj(['error', 'warning', 'info', 'debug', 'start', 'stop']); + reader = new XmlReaderV1(logger, xml); + }); + + it('should be created', function () { + expect(reader).toBeTruthy(); + }); + + it('reads the AAS environment from a xml source', function () { + const environment = reader.readEnvironment(); + expect(environment).toBeDefined(); + }); + }); +}); diff --git a/projects/aas-server/src/test/packages/xml-reader.spec.ts b/projects/aas-server/src/test/packages/xml-reader.spec.ts index 7020e814..ba280d21 100644 --- a/projects/aas-server/src/test/packages/xml-reader.spec.ts +++ b/projects/aas-server/src/test/packages/xml-reader.spec.ts @@ -7,62 +7,34 @@ *****************************************************************************/ import { readFile } from 'fs/promises'; -import { resolve } from 'path'; +import { resolve } from 'path/posix'; import { Logger } from '../../app/logging/logger.js'; -import { XmlReader } from '../../app/packages/xml-reader.js'; import { createSpyObj } from '../utils.js'; import { describe, beforeAll, beforeEach, it, expect, jest } from '@jest/globals'; +import { XmlReader } from '../../app/packages/xml-reader.js'; describe('XmlReader', function () { - describe('with default namespace v2.0', function () { - let reader: XmlReader; - let logger: jest.Mocked; - let xml: string; - let path: string; - - beforeAll(async function () { - path = resolve('./src/test/assets/aas-default-namespace.xml'); - xml = (await readFile(path)).toString(); - }); - - beforeEach(function () { - logger = createSpyObj(['error', 'warning', 'info', 'debug', 'start', 'stop']); - reader = new XmlReader(logger, xml); - }); - - it('should be created', function () { - expect(reader).toBeTruthy(); - }); - - it('reads the AAS environment from a xml source', function () { - const environment = reader.readEnvironment(); - expect(environment).toBeDefined(); - }); + let reader: XmlReader; + let logger: jest.Mocked; + let xml: string; + let path: string; + + beforeAll(async function () { + path = resolve('./src/test/assets/aas-example-v3.xml'); + xml = (await readFile(path)).toString(); }); - describe('with prefix namespace v1.0', function () { - let reader: XmlReader; - let logger: jest.Mocked; - let xml: string; - let path: string; - - beforeAll(async function () { - path = resolve('./src/test/assets/aas-prefix-namespace.xml'); - xml = (await readFile(path)).toString(); - }); - - beforeEach(function () { - logger = createSpyObj(['error', 'warning', 'info', 'debug', 'start', 'stop']); - reader = new XmlReader(logger, xml); - }); + beforeEach(function () { + logger = createSpyObj(['error', 'warning', 'info', 'debug', 'start', 'stop']); + reader = new XmlReader(logger, xml); + }); - it('should be created', function () { - expect(reader).toBeTruthy(); - }); + it('should be created', function () { + expect(reader).toBeTruthy(); + }); - it('reads the AAS environment from a xml source', function () { - const environment = reader.readEnvironment(); - expect(environment).toBeDefined(); - }); + it('reads the AAS environment from a xml source', function () { + const environment = reader.readEnvironment(); + expect(environment).toBeDefined(); }); }); diff --git a/projects/aas-server/src/test/template/template-storage.spec.ts b/projects/aas-server/src/test/template/template-storage.spec.ts index 54933973..a809aa61 100644 --- a/projects/aas-server/src/test/template/template-storage.spec.ts +++ b/projects/aas-server/src/test/template/template-storage.spec.ts @@ -63,7 +63,6 @@ describe('TemplateStorage', function () { modelType: 'Submodel', format: '.json', endpoint: { type: 'file', address: 'submodel.json' }, - template: null, }, ] as TemplateDescriptor[]); diff --git a/projects/common/src/lib/aas.ts b/projects/common/src/lib/aas.ts index 3a586196..2711024f 100644 --- a/projects/common/src/lib/aas.ts +++ b/projects/common/src/lib/aas.ts @@ -330,7 +330,7 @@ export interface Range extends DataElement { /** Metainformation if SubmodelElement is DataElement */ export type Category = 'CONSTANT' | 'PARAMETER' | 'VARIABLE'; -export interface Referable { +export interface Referable extends HasExtensions { category?: string; idShort: string; displayName?: LangString[]; @@ -347,7 +347,7 @@ export interface Reference { } export interface ReferenceElement extends DataElement { - value: Reference; + value?: Reference; } export type ReferenceTypes = 'ExternalReference' | 'ModelReference'; diff --git a/projects/common/src/lib/index.ts b/projects/common/src/lib/index.ts index c219c03c..4c1ddcb1 100644 --- a/projects/common/src/lib/index.ts +++ b/projects/common/src/lib/index.ts @@ -6,6 +6,7 @@ * *****************************************************************************/ +import { AASEndpointType } from './types.js'; import { AssetAdministrationShell, Blob, @@ -18,6 +19,7 @@ import { SubmodelElement, SubmodelElementCollection, } from './aas.js'; +import { isEmpty } from 'lodash-es'; export * from './document.js'; export * from './types.js'; @@ -230,3 +232,54 @@ export function isSubmodelElementCollection(referable?: Referable | null): refer export function isUrlSafeBase64(s: string): boolean { return /^[A-Za-z0-9_-]*[.=]{0,2}$/.test(s); } + +/** + * Gets the endpoint name from the specified URL. + * @param url The endpoint URL. + * @returns The name. + */ +export function getEndpointName(url: string | URL): string { + if (typeof url === 'string') { + url = new URL(url); + } + + const name = url.searchParams.get('name'); + if (name) { + return name; + } + + const pathname = url.pathname; + if (pathname) { + const names = pathname.split('/').filter(item => !isEmpty(item)); + if (names.length > 0) { + return names[names.length - 1]; + } + } + + return url.href.split('?')[0]; +} + +/** + * Gets the endpoint type from the specified URL. + * @param url The URL. + * @returns The endpoint type. + */ +export function getEndpointType(url: string | URL): AASEndpointType { + if (typeof url === 'string') { + url = new URL(url); + } + + switch (url.protocol) { + case 'file:': + return 'FileSystem'; + case 'http:': + case 'https:': { + const pathname = url.pathname; + return pathname && pathname !== '/' ? 'WebDAV' : 'AASServer'; + } + case 'opc.tcp:': + return 'OpcuaServer'; + default: + throw new Error(`Protocol "${url.protocol}" is not supported.`); + } +} diff --git a/projects/common/src/lib/types.ts b/projects/common/src/lib/types.ts index f0957b08..5c36a434 100644 --- a/projects/common/src/lib/types.ts +++ b/projects/common/src/lib/types.ts @@ -46,7 +46,7 @@ export type AASAbbreviation = | 'SML'; /** The kind of AAS container or server. */ -export type AASEndpointType = 'AasxDirectory' | 'AasxServer' | 'OpcuaServer' | 'AASRegistry'; +export type AASEndpointType = 'FileSystem' | 'AASServer' | 'OpcuaServer' | 'WebDAV'; /** The endpoint to an AAS container */ export type AASEndpoint = { @@ -113,9 +113,8 @@ export interface TemplateDescriptor { idShort: string; id?: string; endpoint?: Endpoint; - format?: '.json' | '.xml'; + format?: '.json' | '.xml' | '.aasx'; modelType: aas.ModelType | ''; - template: aas.Referable | null; } /** Represents a named source of Asset Administration Shells. */ diff --git a/projects/common/src/test/index.spec.ts b/projects/common/src/test/index.spec.ts index 3088108c..30512179 100644 --- a/projects/common/src/test/index.spec.ts +++ b/projects/common/src/test/index.spec.ts @@ -11,6 +11,8 @@ import { createSpyObj } from './utils.js'; import { aas, equalUrls, + getEndpointName, + getEndpointType, isAssetAdministrationShell, isBlob, isMultiLanguageProperty, @@ -269,4 +271,48 @@ describe('index', function () { ).toBeTruthy(); }); }); + + describe('getEndpointName', function () { + it('gets the endpoint name from an URL string', function () { + expect(getEndpointName('http://localhost:1234/?name=Test')).toEqual('Test'); + }); + + it('gets the endpoint name from a URL', function () { + expect(getEndpointName(new URL('http://localhost:1234/?name=Test'))).toEqual('Test'); + }); + + it('gets the default name of an AASX server', function () { + expect(getEndpointName('http://localhost:1234/')).toEqual('http://localhost:1234/'); + }); + + it('gets the default name of an cloud server', function () { + expect(getEndpointName('http://localhost:1234/endpoints/samples')).toEqual('samples'); + }); + + it('gets the default name of an OPCUA server', function () { + expect(getEndpointName(new URL('opc.tcp://172.16.160.178:30001/I4AASServer'))).toEqual('I4AASServer'); + }); + + it('gets the default name of an file system directory', function () { + expect(getEndpointName('file:///endpoints/samples')).toEqual('samples'); + }); + }); + + describe('getEndpointType', function () { + it('gets the endpoint type from an URL string', function () { + expect(getEndpointType('http://localhost:1234/')).toEqual('AASServer'); + }); + + it('gets the endpoint type from a URL', function () { + expect(getEndpointType(new URL('opc.tcp://localhost:1234/I4AASServer'))).toEqual('OpcuaServer'); + }); + + it('gets "AASServer" as default', function () { + expect(getEndpointType('http://localhost:1234/endpoints/sample')).toEqual('WebDAV'); + }); + + it('gets "WebDAV" as default', function () { + expect(getEndpointType('file:///endpoints/samples')).toEqual('FileSystem'); + }); + }); });