diff --git a/packages/compas-open-scd/public/nsdoc/README.md b/packages/compas-open-scd/public/nsdoc/README.md
index 8df94c234e..1ae14a7237 100644
--- a/packages/compas-open-scd/public/nsdoc/README.md
+++ b/packages/compas-open-scd/public/nsdoc/README.md
@@ -1,6 +1,17 @@
Directory containing fixed nsdoc for the 'Validation' plugin:
-- IEC_61850-7-2_2007B3-en.nsdoc
-- IEC_61850-7-3_2007B3-en.nsdoc
-- IEC_61850-7-4_2007B3-en.nsdoc
-- IEC_61850-8-1_2003A2-en.nsdoc
+- IEC_61850-7-2_2007XX-en.nsdoc
+- IEC_61850-7-3_2007XX-en.nsdoc
+- IEC_61850-7-4_2007XX-en.nsdoc
+- IEC_61850-8-1_2003XX-en.nsdoc
+
+Where `XX` represents the revision and release values.
+
+This directory should also contain a `manifest.json` file listing all available NSDOC files for dynamic discovery. If the manifest is not present, the system will fall back to pattern-based file discovery.
+
+## Fallback pattern discovery
+
+When manifest.json is not available, the service checks for files using these patterns:
+
+- Standards 7-2, 7-3, 7-4: `2007B5` to `2007B9` (stops at first found)
+- Standard 8-1: `2003A2` to `2003A9` (stops at first found)
diff --git a/packages/compas-open-scd/public/nsdoc/manifest.json.example b/packages/compas-open-scd/public/nsdoc/manifest.json.example
new file mode 100644
index 0000000000..52a354ef48
--- /dev/null
+++ b/packages/compas-open-scd/public/nsdoc/manifest.json.example
@@ -0,0 +1,8 @@
+
+[
+ "IEC_61850-7-2_2007B5-en.nsdoc",
+ "IEC_61850-7-3_2007B5-en.nsdoc",
+ "IEC_61850-7-4_2007B5-en.nsdoc",
+ "IEC_61850-8-1_2003A2-en.nsdoc"
+]
+
diff --git a/packages/compas-open-scd/public/xml/IEC_61850-7-2_2007B3.nsd b/packages/compas-open-scd/public/xml/IEC_61850-7-2_2007B5.nsd
similarity index 98%
rename from packages/compas-open-scd/public/xml/IEC_61850-7-2_2007B3.nsd
rename to packages/compas-open-scd/public/xml/IEC_61850-7-2_2007B5.nsd
index 9635bf57ce..ba98cb1ef4 100644
--- a/packages/compas-open-scd/public/xml/IEC_61850-7-2_2007B3.nsd
+++ b/packages/compas-open-scd/public/xml/IEC_61850-7-2_2007B5.nsd
@@ -5,10 +5,15 @@
id="IEC 61850-7-2"
version="2007"
revision="B"
- release="3"
- umlVersion="WG10built4"
- umlDate="2019-10-02T00:00:00Z"
- publicationStage="IS">
+ release="5"
+ umlVersion="WG10built12"
+ umlDate="2024-01-15"
+ publicationStage="IS"
+ appVersion="j61850DocBuilder 02.03 based on jCleanCim noNS beta9.3 (derived from jCleanCim 02-02)"
+ namespaceType="basic"
+ nsdVersion="2017"
+ nsdRevision="B"
+ nsdRelease="5">
COPYRIGHT (c) IEC, www.iec.ch/tc57/supportdocuments. This version of this NSD is part of IEC_61850-7-2:2010 Edition 2.1; see the IEC_61850-7-2:2010 Edition 2.1 for full legal notices. In case of any differences between the here-below code and the IEC published content, the here-below definition supersedes the IEC publication; it may contain updates. See history files. The whole document has to be taken into account to have a full description of this code component.
@@ -17,8 +22,8 @@
+ revision="B"
+ tissues="1781, 1782, 1801, 1841, 1847, 1822"/>
@@ -531,4 +536,4 @@
presCond="M"/>
-
\ No newline at end of file
+
diff --git a/packages/compas-open-scd/public/xml/IEC_61850-7-3_2007B3.nsd b/packages/compas-open-scd/public/xml/IEC_61850-7-3_2007B5.nsd
similarity index 99%
rename from packages/compas-open-scd/public/xml/IEC_61850-7-3_2007B3.nsd
rename to packages/compas-open-scd/public/xml/IEC_61850-7-3_2007B5.nsd
index 777a13cf59..7ec83fc09f 100644
--- a/packages/compas-open-scd/public/xml/IEC_61850-7-3_2007B3.nsd
+++ b/packages/compas-open-scd/public/xml/IEC_61850-7-3_2007B5.nsd
@@ -5,10 +5,15 @@
id="IEC 61850-7-3"
version="2007"
revision="B"
- release="3"
- umlVersion="WG10built3"
- umlDate="2019-10-02T00:00:00Z"
- publicationStage="IS">
+ release="5"
+ umlVersion="WG10built12"
+ umlDate="2024-02-12"
+ publicationStage="IS"
+ appVersion="j61850DocBuilder 02.03 based on jCleanCim noNS beta9.3 (derived from jCleanCim 02-02)"
+ namespaceType="basic"
+ nsdVersion="2017"
+ nsdRevision="B"
+ nsdRelease="5">
COPYRIGHT (c) IEC, www.iec.ch/tc57/supportdocuments. This version of this NSD is part of IEC_61850-7-3:2010 Edition 2.1; see the IEC_61850-7-3:2010 Edition 2.1 for full legal notices. In case of any differences between the here-below code and the IEC published content, the here-below definition supersedes the IEC publication; it may contain updates. See history files. The whole document has to be taken into account to have a full description of this code component.
@@ -17,9 +22,12 @@
-
+ revision="B"
+ tissues="1716, 1730, 1783, 1785, 1807, 1829, 1840, 1851, 1852, 1889, 1900"/>
+
@@ -232,7 +240,7 @@
descID="IEC61850_7_3.DAEnums::MultiplierKind.n.desc"/>
+ descID="IEC61850_7_3.DAEnums::MultiplierKind.µ.desc"/>
@@ -385,7 +393,7 @@
descID="IEC61850_7_3.DAEnums::SIUnitKind.Bq.desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.°C.desc"/>
@@ -436,31 +444,31 @@
descID="IEC61850_7_3.DAEnums::SIUnitKind.Pa.desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.m².desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.m³.desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.m_per_s².desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.m³_per_s.desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.m_per_m³.desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.kg_per_m³.desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.m²_per_s.desc"/>
@@ -478,10 +486,10 @@
descID="IEC61850_7_3.DAEnums::SIUnitKind.rad_per_s.desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.W_per_m².desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.J_per_m².desc"/>
@@ -516,16 +524,16 @@
descID="IEC61850_7_3.DAEnums::SIUnitKind.Vs.desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.V².desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.A².desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.A²t.desc"/>
@@ -549,7 +557,7 @@
descID="IEC61850_7_3.DAEnums::SIUnitKind.char_per_s.desc"/>
+ descID="IEC61850_7_3.DAEnums::SIUnitKind.kgm².desc"/>
@@ -1429,7 +1437,8 @@
type="Timestamp"
dchg="true"
descID="IEC61850_7_3.CDCStatusInfo::BCR.strTm.desc"
- presCond="O"/>
+ presCond="OF"
+ presCondArgs="frVal"/>
+ presCond="MO"
+ presCondArgs="db"/>
+ presCond="MO"
+ presCondArgs="db"/>
-
+
-
+
+ release="5"
+ umlVersion="WG10built12"
+ umlDate="2024-02-14"
+ publicationStage="IS"
+ appVersion="j61850DocBuilder 02.03 based on jCleanCim noNS beta9.3 (derived from jCleanCim 02-02)"
+ namespaceType="basic"
+ nsdVersion="2017"
+ nsdRevision="B"
+ nsdRelease="5">
- COPYRIGHT (c) IEC, www.iec.ch/tc57/supportdocuments. This version of this NSD is part of IEC_61850-7-4:2007; see the IEC_61850-7-4:2007 for full legal notices. In case of any differences between the here-below code and the IEC published content, the here-below definition supersedes the IEC publication; it may contain updates. See history files. The whole document has to be taken into account to have a full description of this code component.
+ COPYRIGHT (c) IEC, www.iec.ch/tc57/supportdocuments. This version of this NSD is part of IEC_61850-7-4:2020 Edition 2.1; see the IEC_61850-7-4:2020 Edition 2.1 for full legal notices. In case of any differences between the here-below code and the IEC published content, the here-below definition supersedes the IEC publication; it may contain updates. See history files. The whole document has to be taken into account to have a full description of this code component.
See www.iec.ch/CCv1 for copyright details.
-
+
+
@@ -953,6 +962,7 @@
+
@@ -1164,11 +1174,9 @@
-
-
-
+
+
+
@@ -1611,7 +1619,8 @@
-
+
+
@@ -1957,12 +1966,6 @@
-
@@ -3547,6 +3554,20 @@
descID="IEC61850_7_4.LNGroupL::LTRK.SgcbTrk.desc"
presCond="O"
dsPresCond="na"/>
+
+
-
-
-
+
-
-
-
+
+
-
+
+
+
{
- const element: Element = document.createElement(name);
- element.textContent = textContent;
-
- return element;
-};
-
-/** TODO: Make this return JSON */
-export function CompasNSDocFileService() {
- return {
- listNsdocFiles(): Promise {
- const document: XMLDocument = new DOMParser().parseFromString(
- '',
- 'text/xml'
- );
+interface NsDocFileInfo {
+ id: string;
+ version: string;
+ revision: string;
+ release: string;
+ filename: string;
+ fullVersion: string;
+}
+
+interface NsDocFileResponse {
+ id: string;
+ nsdocId: string;
+ filename: string;
+ checksum: string;
+}
- nsDocfiles.forEach(nsDocFile => {
- const nsDocFileElement: Element = document.createElement('NsdocFile');
+interface NsDocListResponse {
+ files: NsDocFileResponse[];
+}
- nsDocFileElement.appendChild(
- createElement('Id', nsDocFile.id, document)
- );
- nsDocFileElement.appendChild(
- createElement('NsdocId', nsDocFile.name, document)
- );
- nsDocFileElement.appendChild(
- createElement('Checksum', nsDocFile.id, document)
- );
- nsDocFileElement.appendChild(
- createElement('Filename', nsDocFile.filename, document)
+interface NsdocContentResponse {
+ content: string;
+}
+
+function generateIdFromName(name: string): string {
+ let hash = 0;
+ for (let i = 0; i < name.length; i++) {
+ const char = name.charCodeAt(i);
+ hash = (hash << 5) - hash + char;
+ hash = hash & hash;
+ }
+
+ const hashStr = Math.abs(hash).toString(16).padStart(8, '0');
+ return `${hashStr.slice(0, 8)}-${hashStr.slice(0, 4)}-4${hashStr.slice(
+ 1,
+ 4
+ )}-${hashStr.slice(0, 4)}-${hashStr}${hashStr.slice(0, 4)}`;
+}
+
+function parseVersionNumber(
+ version: string,
+ revision: string,
+ release: string
+): number {
+ const versionNum = parseInt(version) || 0;
+ const revisionNum = revision.charCodeAt(0) - 65;
+ const releaseNum = parseInt(release) || 0;
+
+ return versionNum * 1000000 + revisionNum * 1000 + releaseNum;
+}
+
+function parseNsDocFilename(filename: string): NsDocFileInfo | null {
+ const match = filename.match(
+ /^IEC_61850-([0-9]+-[0-9]+)_(\d{4})([A-Z])(\d+)-en\.nsdoc$/
+ );
+ if (match) {
+ const [, standardPart, version, revision, release] = match;
+ const id = `IEC 61850-${standardPart}`;
+ const fullVersion = `${version}${revision}${release}`;
+
+ return {
+ id,
+ version,
+ revision,
+ release,
+ filename,
+ fullVersion,
+ };
+ }
+ return null;
+}
+
+async function isValidNsDocFile(filename: string): Promise {
+ try {
+ const response = await fetch(`/public/nsdoc/${filename}`);
+ if (!response.ok) {
+ return false;
+ }
+
+ const content = await response.text();
+ const doc = new DOMParser().parseFromString(content, 'text/xml');
+ const nsElement = doc.querySelector('NSDoc');
+ const xmlns = nsElement?.getAttribute('xmlns');
+
+ return xmlns === 'http://www.iec.ch/61850/2016/NSD';
+ } catch (error) {
+ return false;
+ }
+}
+
+// Get NSDOC files from manifest.json
+async function getNsDocFilesFromManifest(): Promise {
+ try {
+ const manifestResponse = await fetch('/public/nsdoc/manifest.json');
+ if (!manifestResponse.ok) {
+ return [];
+ }
+
+ const manifest = await manifestResponse.json();
+ if (!Array.isArray(manifest)) {
+ return [];
+ }
+
+ const nsdocFiles = manifest.filter(
+ (filename: unknown) =>
+ typeof filename === 'string' && filename.endsWith('-en.nsdoc')
+ ) as string[];
+
+ return nsdocFiles;
+ } catch (error) {
+ return [];
+ }
+}
+
+// Discover NSDOC files using pattern-based approach (fallback)
+async function getNsDocFilesByPattern(): Promise {
+ const discoveredFiles: string[] = [];
+
+ const standards2007 = ['7-2', '7-3', '7-4'];
+ for (const standard of standards2007) {
+ for (let release = 5; release <= 9; release++) {
+ const filename = `IEC_61850-${standard}_2007B${release}-en.nsdoc`;
+ try {
+ const response = await fetch(`/public/nsdoc/${filename}`);
+ if (response.ok) {
+ discoveredFiles.push(filename);
+ break;
+ }
+ } catch (e) {
+ // Continue to next version
+ }
+ }
+ }
+
+ for (let release = 2; release <= 9; release++) {
+ const filename = `IEC_61850-8-1_2003A${release}-en.nsdoc`;
+ try {
+ const response = await fetch(`/public/nsdoc/${filename}`);
+ if (response.ok) {
+ discoveredFiles.push(filename);
+ break;
+ }
+ } catch (e) {
+ // Continue to next version
+ }
+ }
+
+ return discoveredFiles;
+}
+
+async function parseAndValidateNsDocFiles(
+ filenames: string[]
+): Promise {
+ const parsedFiles: NsDocFileInfo[] = [];
+
+ for (const filename of filenames) {
+ const fileInfo = parseNsDocFilename(filename);
+ if (fileInfo) {
+ const isValid = await isValidNsDocFile(filename);
+ if (isValid) {
+ parsedFiles.push(fileInfo);
+ } else {
+ console.warn(
+ `Skipping invalid NSDOC file: ${filename} (missing or incorrect schema)`
);
+ }
+ }
+ }
+
+ return parsedFiles;
+}
- document
- .querySelector('NsdocListResponse')!
- .appendChild(nsDocFileElement);
- });
+function selectLatestVersions(parsedFiles: NsDocFileInfo[]): NsDocFile[] {
+ const standardsMap = new Map();
+
+ for (const fileInfo of parsedFiles) {
+ const currentFileInMap = standardsMap.get(fileInfo.id);
+
+ if (!currentFileInMap) {
+ standardsMap.set(fileInfo.id, fileInfo);
+ } else {
+ const currentVersionNum = parseVersionNumber(
+ currentFileInMap.version,
+ currentFileInMap.revision,
+ currentFileInMap.release
+ );
+ const newVersionNum = parseVersionNumber(
+ fileInfo.version,
+ fileInfo.revision,
+ fileInfo.release
+ );
- return Promise.resolve(document);
+ if (newVersionNum > currentVersionNum) {
+ standardsMap.set(fileInfo.id, fileInfo);
+ }
+ }
+ }
+
+ return Array.from(standardsMap.values()).map(fileInfo => ({
+ filename: fileInfo.filename,
+ name: fileInfo.id,
+ id: generateIdFromName(fileInfo.id + fileInfo.fullVersion),
+ }));
+}
+
+async function getNsDocFiles(): Promise {
+ try {
+ let nsdocFiles = await getNsDocFilesFromManifest();
+ if (nsdocFiles.length === 0) {
+ nsdocFiles = await getNsDocFilesByPattern();
+ }
+
+ if (nsdocFiles.length === 0) {
+ console.warn(
+ 'No NSDOC files found using either manifest or pattern-based discovery'
+ );
+ return [];
+ }
+
+ const parsedFiles = await parseAndValidateNsDocFiles(nsdocFiles);
+
+ return selectLatestVersions(parsedFiles);
+ } catch (error) {
+ console.error('Failed to load NSDOC files:', error);
+ return [];
+ }
+}
+
+export function CompasNSDocFileService(): {
+ listNsdocFiles(): Promise;
+ getNsdocFile(id: string): Promise;
+} {
+ return {
+ async listNsdocFiles(): Promise {
+ const nsDocFiles = await getNsDocFiles();
+
+ return {
+ files: nsDocFiles.map((nsDocFile: NsDocFile) => ({
+ id: nsDocFile.id,
+ nsdocId: nsDocFile.name,
+ filename: nsDocFile.filename,
+ checksum: nsDocFile.id,
+ })),
+ };
},
- getNsdocFile(id: string): Promise {
- const nsDocFile: NsDocFile = nsDocfiles.find(f => f.id === id)!;
+ async getNsdocFile(id: string): Promise {
+ const nsDocFiles = await getNsDocFiles();
+ const nsDocFile: NsDocFile | undefined = nsDocFiles.find(
+ (f: NsDocFile) => f.id === id
+ );
if (!nsDocFile) {
return Promise.reject(`Unable to find nsDoc file with id ${id}`);
}
- return fetch(`./public/nsdoc/${nsDocFile.filename}`)
+
+ const content = await fetch(`/public/nsdoc/${nsDocFile.filename}`)
.catch(handleError)
- .then(handleResponse)
- .then(res => {
- const document: XMLDocument = new DOMParser().parseFromString(
- '',
- 'text/xml'
- );
-
- document
- .querySelector('NsdocResponse')!
- .appendChild(createElement('NsdocFile', res, document));
-
- return document;
- });
+ .then(handleResponse);
+
+ return {
+ content,
+ };
},
};
}
diff --git a/packages/compas-open-scd/src/compas/CompasNsdoc.ts b/packages/compas-open-scd/src/compas/CompasNsdoc.ts
index f9ecb37875..eb2bb91b9e 100644
--- a/packages/compas-open-scd/src/compas/CompasNsdoc.ts
+++ b/packages/compas-open-scd/src/compas/CompasNsdoc.ts
@@ -4,10 +4,24 @@ import {
createLogEvent,
createNSDocLogEvent,
} from '../compas-services/foundation.js';
-import { CompasSclValidatorService } from '../compas-services/CompasValidatorService.js';
import { CompasNSDocFileService } from '../compas-services/CompasNSDocFileService.js';
import { newLoadNsdocEvent } from '@openscd/core/foundation/deprecated/settings.js';
+interface NsdocFileResponse {
+ id: string;
+ nsdocId: string;
+ filename: string;
+ checksum: string;
+}
+
+interface NsdocListResponse {
+ files: NsdocFileResponse[];
+}
+
+interface NsdocContentResponse {
+ content: string;
+}
+
/**
* Load a single entry. Use the nsdocId to look in the Local Storage, if already loaded,
* and if the checksum is the same.
@@ -37,10 +51,8 @@ async function processNsdocFile(
console.info(`Loading NSDoc File '${nsdocId}' with ID '${id}'.`);
await CompasNSDocFileService()
.getNsdocFile(id)
- .then(document => {
- const nsdocContent =
- document.querySelectorAll('NsdocFile').item(0).textContent ?? '';
- component.dispatchEvent(newLoadNsdocEvent(nsdocContent, filename));
+ .then((response: NsdocContentResponse) => {
+ component.dispatchEvent(newLoadNsdocEvent(response.content, filename));
localStorage.setItem(checksumKey, checksum);
})
.catch(() => {
@@ -58,18 +70,11 @@ async function processNsdocFile(
export async function loadNsdocFiles(component: Element): Promise {
await CompasNSDocFileService()
.listNsdocFiles()
- .then(response => {
- Array.from(response.querySelectorAll('NsdocFile') ?? []).forEach(
- nsdocFile => {
- const id = nsdocFile.querySelector('Id')!.textContent ?? '';
- const nsdocId = nsdocFile.querySelector('NsdocId')!.textContent ?? '';
- const filename =
- nsdocFile.querySelector('Filename')!.textContent ?? '';
- const checksum =
- nsdocFile.querySelector('Checksum')!.textContent ?? '';
- processNsdocFile(component, id, nsdocId, filename, checksum);
- }
- );
+ .then((response: NsdocListResponse) => {
+ response.files.forEach(nsdocFile => {
+ const { id, nsdocId, filename, checksum } = nsdocFile;
+ processNsdocFile(component, id, nsdocId, filename, checksum);
+ });
})
.catch(reason => {
createLogEvent(component, reason);
diff --git a/packages/compas-open-scd/test/unit/compas-services/CompasNSDocFileService.test.ts b/packages/compas-open-scd/test/unit/compas-services/CompasNSDocFileService.test.ts
index dedb73fe0e..d4b91a9cde 100644
--- a/packages/compas-open-scd/test/unit/compas-services/CompasNSDocFileService.test.ts
+++ b/packages/compas-open-scd/test/unit/compas-services/CompasNSDocFileService.test.ts
@@ -1,36 +1,250 @@
import { expect } from '@open-wc/testing';
+import { stub, restore } from 'sinon';
import { CompasNSDocFileService } from '../../../src/compas-services/CompasNSDocFileService.js';
-describe('compas-nsdocfile-service', () => {
- it('Should list all NSDoc files', async () => {
- const res = await CompasNSDocFileService().listNsdocFiles();
+interface NsDocFileResponse {
+ id: string;
+ nsdocId: string;
+ filename: string;
+ checksum: string;
+}
- const nsDocFiles: Element[] = Array.from(res.querySelectorAll('NsdocFile'));
+describe('CompasNSDocFileService', () => {
+ let fetchStub: sinon.SinonStub;
- expect(nsDocFiles.length).to.equal(4);
+ beforeEach(() => {
+ fetchStub = stub(window, 'fetch');
});
- it('Should fail on invalid request', done => {
- const id = '315b02ac-c4aa-4495-9b4f-f7175a75c315';
- CompasNSDocFileService()
- .getNsdocFile(id)
- .then(() => done('Failed'))
- .catch(err => {
- expect(err.status).to.equal(404);
- expect(err.type).to.equal('NotFoundError');
- done();
+ afterEach(() => {
+ restore();
+ });
+
+ it('should list all NSDoc files', async () => {
+ const mockManifest = [
+ 'IEC_61850-7-2_2003A2-en.nsdoc',
+ 'IEC_61850-7-3_2007B3-en.nsdoc',
+ 'IEC_61850-8-1_2011A1-en.nsdoc',
+ ];
+
+ const validNsdocContent = `
+
+ Test
+ `;
+
+ fetchStub.callsFake((url: string) => {
+ if (url.includes('manifest.json')) {
+ return Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve(mockManifest),
+ });
+ }
+
+ return Promise.resolve({
+ ok: true,
+ text: () => Promise.resolve(validNsdocContent),
+ });
+ });
+
+ const service = CompasNSDocFileService();
+ const result = await service.listNsdocFiles();
+
+ expect(result).to.have.property('files');
+ expect(result.files).to.be.an('array');
+ expect(result.files.length).to.equal(3);
+
+ result.files.forEach((file: NsDocFileResponse) => {
+ expect(file).to.have.property('id');
+ expect(file).to.have.property('nsdocId');
+ expect(file).to.have.property('filename');
+ expect(file).to.have.property('checksum');
+ expect(file.filename).to.match(
+ /^IEC_61850-[0-9]+-[0-9]+_\d{4}[A-Z]\d+-en\.nsdoc$/
+ );
+ });
+ });
+
+ it('should get NSDOC file content', async () => {
+ const mockManifest = ['IEC_61850-7-2_2003A2-en.nsdoc'];
+ const validNsdocContent = `
+
+ Test
+ `;
+
+ fetchStub.callsFake((url: string) => {
+ if (url.includes('manifest.json')) {
+ return Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve(mockManifest),
+ });
+ }
+
+ return Promise.resolve({
+ ok: true,
+ text: () => Promise.resolve(validNsdocContent),
});
+ });
+
+ const service = CompasNSDocFileService();
+
+ const listResult = await service.listNsdocFiles();
+ const firstFile = listResult.files[0];
+ const contentResult = await service.getNsdocFile(firstFile.id);
+ const doc = new DOMParser().parseFromString(
+ contentResult.content,
+ 'text/xml'
+ );
+ const nsElement = doc.querySelector('NSDoc');
+
+ expect(contentResult).to.have.property('content');
+ expect(nsElement).to.not.be.null;
+ expect(nsElement?.getAttribute('xmlns')).to.equal(
+ 'http://www.iec.ch/61850/2016/NSD'
+ );
+ expect(nsElement?.getAttribute('id')).to.equal('IEC 61850-7-2');
+
+ const docElement = doc.querySelector('Doc');
+ expect(docElement).to.not.be.null;
+ expect(docElement?.getAttribute('id')).to.equal('IEC61850-7-2');
});
- it('Should fail on invalid id', done => {
- const id = '1';
-
- CompasNSDocFileService()
- .getNsdocFile(id)
- .then(() => done('Failed'))
- .catch(err => {
- expect(err).to.equal(`Unable to find nsDoc file with id ${id}`);
- done();
+
+ it('should fail on missing file', async () => {
+ fetchStub.callsFake((url: string) => {
+ if (url.includes('manifest.json')) {
+ return Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve([]),
+ });
+ }
+
+ return Promise.resolve({
+ ok: false,
+ status: 404,
});
+ });
+
+ const service = CompasNSDocFileService();
+ const invalidId = 'invalid-id-123';
+
+ try {
+ await service.getNsdocFile(invalidId);
+ expect.fail('Should have thrown an error');
+ } catch (error) {
+ expect(error).to.equal(`Unable to find nsDoc file with id ${invalidId}`);
+ }
+ });
+
+ it('should handle version selection correctly', async () => {
+ const mockManifest = [
+ 'IEC_61850-7-2_2003A2-en.nsdoc',
+ 'IEC_61850-7-2_2003B1-en.nsdoc',
+ 'IEC_61850-8-1_2011A1-en.nsdoc',
+ 'IEC_61850-8-1_2011B2-en.nsdoc',
+ ];
+
+ const validNsdocContent72 = `
+
+ Test 7-2
+ `;
+
+ const validNsdocContent81 = `
+
+ Test 8-1
+ `;
+
+ fetchStub.callsFake((url: string) => {
+ if (url.includes('manifest.json')) {
+ return Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve(mockManifest),
+ });
+ }
+
+ const filename = url.split('/').pop() || '';
+ if (filename.includes('7-2')) {
+ return Promise.resolve({
+ ok: true,
+ text: () => Promise.resolve(validNsdocContent72),
+ });
+ } else if (filename.includes('8-1')) {
+ return Promise.resolve({
+ ok: true,
+ text: () => Promise.resolve(validNsdocContent81),
+ });
+ }
+
+ return Promise.resolve({
+ ok: false,
+ status: 404,
+ });
+ });
+
+ const service = CompasNSDocFileService();
+ const result = await service.listNsdocFiles();
+
+ const filesByStandard = new Map();
+ result.files.forEach((file: NsDocFileResponse) => {
+ if (!filesByStandard.has(file.nsdocId)) {
+ filesByStandard.set(file.nsdocId, []);
+ }
+ filesByStandard.get(file.nsdocId)!.push(file);
+ });
+
+ filesByStandard.forEach((files, standardId) => {
+ expect(files).to.have.length(
+ 1,
+ `Standard ${standardId} should have only one file (latest version)`
+ );
+ });
+ });
+
+ it('should handle schema validation correctly', async () => {
+ const mockManifest = [
+ 'IEC_61850-7-2_2003A2-en.nsdoc',
+ 'IEC_61850-INVALID_2023A1-en.nsdoc',
+ ];
+
+ const validNsdocContent = `
+
+ Test
+ `;
+
+ const invalidNsdocContent = `
+
+ Invalid
+ `;
+
+ fetchStub.callsFake((url: string) => {
+ if (url.includes('manifest.json')) {
+ return Promise.resolve({
+ ok: true,
+ json: () => Promise.resolve(mockManifest),
+ });
+ }
+
+ const filename = url.split('/').pop() || '';
+ if (filename.includes('INVALID')) {
+ return Promise.resolve({
+ ok: true,
+ text: () => Promise.resolve(invalidNsdocContent),
+ });
+ }
+
+ return Promise.resolve({
+ ok: true,
+ text: () => Promise.resolve(validNsdocContent),
+ });
+ });
+
+ const service = CompasNSDocFileService();
+ const result = await service.listNsdocFiles();
+
+ expect(result.files).to.be.an('array');
+
+ const invalidFile = result.files.find((f: NsDocFileResponse) =>
+ f.filename.includes('INVALID')
+ );
+ expect(invalidFile).to.be.undefined;
});
});