From 0f7a40a41367746c23858cf230189679be1c56e5 Mon Sep 17 00:00:00 2001 From: Jakob Vogelsang Date: Mon, 31 Mar 2025 12:01:56 +0200 Subject: [PATCH] feat(nsdToJson): add CDC support --- tDataTypeTemplates/nsdToJson.spec.ts | 18 +- tDataTypeTemplates/nsdToJson.ts | 154 +++++++++++++----- tDataTypeTemplates/nsdToJson/testJson/ARIS.ts | 24 +-- tDataTypeTemplates/nsdToJson/testJson/FSCH.ts | 6 +- tDataTypeTemplates/nsdToJson/testJson/IARC.ts | 26 +-- tDataTypeTemplates/nsdToJson/testJson/PTOF.ts | 22 +-- .../nsdToJson/testJson/{TSC.ts => TCS.ts} | 2 +- tDataTypeTemplates/nsdToJson/testNsdJson.ts | 69 ++++++++ 8 files changed, 209 insertions(+), 112 deletions(-) rename tDataTypeTemplates/nsdToJson/testJson/{TSC.ts => TCS.ts} (97%) diff --git a/tDataTypeTemplates/nsdToJson.spec.ts b/tDataTypeTemplates/nsdToJson.spec.ts index 5ff750a..4ad093b 100644 --- a/tDataTypeTemplates/nsdToJson.spec.ts +++ b/tDataTypeTemplates/nsdToJson.spec.ts @@ -7,9 +7,9 @@ import { nsd7420, nsd81, } from "../foundation/codecomponents/nsds.js"; -import { lnClass74, lnClassData } from "./nsdToJson/testNsdJson.js"; +import { cdcData, lnClass74, lnClassData } from "./nsdToJson/testNsdJson.js"; -import { nsdToJson } from "./nsdToJson.js"; +import { nsdToJson, supportedCdc } from "./nsdToJson.js"; const doc72 = new DOMParser().parseFromString(nsd72, "application/xml"); const doc73 = new DOMParser().parseFromString(nsd73, "application/xml"); @@ -21,6 +21,9 @@ describe("NSD to Json parsing function", () => { it("return undefined for invalid ln class", () => expect(nsdToJson("invalid")).to.be.undefined); + it("return undefined for unsupported CDC", () => + expect(nsdToJson("ENS")).to.be.undefined); + it("returns object that compares well to static 7-4 ln classes", async () => { lnClass74.forEach((lnClass) => { const data = nsdToJson(lnClass)!; @@ -32,6 +35,17 @@ describe("NSD to Json parsing function", () => { }); }).timeout(10000); + it("returns object that compares well to static 7-3 ln classes", async () => { + supportedCdc.forEach((cdc) => { + const data = nsdToJson(cdc)!; + const commonDataClass = cdcData[cdc]; + + Object.keys(commonDataClass).forEach((key) => { + expect(data[key]).to.deep.equal(commonDataClass[key]); + }); + }); + }).timeout(10000); + it("returns object that compares well to static 7-420 classes", async () => { const data = nsdToJson("DSTK"); expect(data).to.not.be.undefined; diff --git a/tDataTypeTemplates/nsdToJson.ts b/tDataTypeTemplates/nsdToJson.ts index b504376..c2e43a6 100644 --- a/tDataTypeTemplates/nsdToJson.ts +++ b/tDataTypeTemplates/nsdToJson.ts @@ -104,6 +104,48 @@ type CdcDescription = { export type LNodeDescription = Record; +export const supportedCdc = [ + "ACD", + "ACT", + "APC", + "ASG", + "BAC", + "BCR", + "BSC", + "CMV", + "DEL", + "DPC", + "DPL", + "HDEL", + "HMV", + "HST", + "HWYE", + "INC", + "INS", + "ISC", + "LPL", + "MV", + "ORG", + "ORS", + "SAV", + "SEC", + "SEQ", + "SPC", + "SPG", + "SPS", + "TCS", + "TSG", + "VSG", + "VSS", + "WYE", +] as const; +const cdcTag = new Set(supportedCdc); +export type SCLTag = (typeof supportedCdc)[number]; + +export function isSupportedCdc(cdc: string): cdc is SCLTag { + return cdcTag.has(cdc); +} + export type NameSpaceDescription = { "72"?: XMLDocument; "73"?: XMLDocument; @@ -123,7 +165,7 @@ const defaultDoc81 = new DOMParser().parseFromString(nsd81, "application/xml"); /** A utility function that returns a JSON containing the structure of a logical node class * as described in the IEC 61850-7-4 and IEC 61850-7-420 as JSON - * @param lnClass the logical node class to be constructed + * @param lnClassOrCdc the logical node class to be constructed * @param nsds user defined NSD files defaulting to * 8-1: 2003A2 * 7-4: 2007B3 @@ -133,19 +175,13 @@ const defaultDoc81 = new DOMParser().parseFromString(nsd81, "application/xml"); * @returns A JSON object represeting NSD information of a logical node */ export function nsdToJson( - lnClass: string, + lnClassOrCdc: string, nsds?: NameSpaceDescription, -): LNodeDescription | undefined { +): LNodeDescription | CdcChildren | undefined { + const doc74 = nsds && nsds["74"] ? nsds["74"] : defaultDoc74; const doc7420 = nsds && nsds["7420"] ? nsds["7420"] : defaultDoc7420; - const nsdLnClass74 = doc74.querySelector(`LNClass[name="${lnClass}"]`); - const nsdLnClass7420 = doc7420.querySelector(`LNClass[name="${lnClass}"]`); - - const nsdLnClass = nsdLnClass74 || nsdLnClass7420; - if (!nsdLnClass) return undefined; - - const lnClassJson: LNodeDescription = {}; - + function getServiceConstructedAttributes( serviceDataAttribute: Element, ): Element[] { @@ -161,10 +197,9 @@ export function nsdToJson( ); } - function getServiceDataAttributes(dataObject: Element): Element[] { + function getServiceDataAttributesType(type : string | null): Element[] { const doc81 = nsds && nsds["81"] ? nsds["81"] : defaultDoc81; - const type = dataObject.getAttribute("type"); return Array.from( doc81 .querySelector(`ServiceCDCs > ServiceCDC[cdc="${type}"]`) @@ -172,7 +207,13 @@ export function nsdToJson( ); } - function getSubDataAttributesType(type: string): Element[] { + function getServiceDataAttributes(dataObject: Element): Element[] { + const type = dataObject.getAttribute("type"); + + return getServiceDataAttributesType(type); + } + + function getSubDataAttributesType(type: string | null): Element[] { const doc73 = nsds && nsds["73"] ? nsds["73"] : defaultDoc73; const doc72 = nsds && nsds["72"] ? nsds["72"] : defaultDoc72; @@ -190,27 +231,14 @@ export function nsdToJson( } function getSubDataAttributes(dataAttribute: Element): Element[] { - const doc73 = nsds && nsds["73"] ? nsds["73"] : defaultDoc73; - const doc72 = nsds && nsds["72"] ? nsds["72"] : defaultDoc72; - const type = dataAttribute.getAttribute("type"); - return Array.from( - doc73 - .querySelector(`ConstructedAttribute[name="${type}"]`) - ?.querySelectorAll(":scope > SubDataAttribute") ?? [], - ).concat( - Array.from( - doc72 - .querySelector(`ConstructedAttribute[name="${type}"]`) - ?.querySelectorAll(":scope > SubDataAttribute") ?? [], - ), - ); + + return getSubDataAttributesType(type) } - function getDataAttributes(dataObject: Element): Element[] { + function getDataAttributesType(type:string|null): Element[] { const doc73 = nsds && nsds["73"] ? nsds["73"] : defaultDoc73; - const type = dataObject.getAttribute("type"); if ( ["CSG", "CURVE", "ENG", "ING", "ASG", "SPG", "TSG", "VSG"].includes( `${type}`, @@ -228,9 +256,15 @@ export function nsdToJson( ); } - function getSubDataObjects(dataObject: Element): Element[] { - const doc73 = nsds && nsds["73"] ? nsds["73"] : defaultDoc73; + function getDataAttributes(dataObject: Element): Element[] { const type = dataObject.getAttribute("type"); + + return getDataAttributesType(type); + } + + function getSubDataObjectsType(type: string | null): Element[] { + const doc73 = nsds && nsds["73"] ? nsds["73"] : defaultDoc73; + return Array.from( doc73 .querySelector(`CDC[name="${type}"]`) @@ -238,6 +272,12 @@ export function nsdToJson( ); } + function getSubDataObjects(dataObject: Element): Element[] { + const type = dataObject.getAttribute("type"); + + return getSubDataObjectsType(type) + } + function getDataObjects(lnClass: Element): Element[] { const baseClass = lnClass.ownerDocument.querySelector( `AbstractLNClass[name="${lnClass.getAttribute("base")}"]`, @@ -579,11 +619,51 @@ export function nsdToJson( return data; } - getDataObjects(nsdLnClass).forEach((dataObject) => { - const name = dataObject.getAttribute("name")!; + function CdcChildren(type: string): CdcChildren { + + const children: CdcChildren = {}; + getSubDataObjectsType(type).forEach((dataObject) => { + const name = dataObject.getAttribute("name")!; + + children[name] = nsdDataObject(dataObject); + }); + + getDataAttributesType(type).forEach((dataAttribute) => { + const name = dataAttribute.getAttribute("name")!; - lnClassJson[name] = nsdDataObject(dataObject); - }); + children[name] = nsdDataAttribute( + dataAttribute, + undefined, + undefined, + ); + }); + + getServiceDataAttributesType(type).forEach((serviceDataAttribute) => { + const name = serviceDataAttribute.getAttribute("name")!; + + children[name] = nsdServiceDataAttribute(serviceDataAttribute); + }); + + return children; + } + + if (lnClassOrCdc === undefined) return; + else if (isSupportedCdc(lnClassOrCdc)) + return CdcChildren(lnClassOrCdc); + else { + const nsdLnClass74 = doc74.querySelector(`LNClass[name="${lnClassOrCdc}"]`); + const nsdLnClass7420 = doc7420.querySelector(`LNClass[name="${lnClassOrCdc}"]`); + + const nsdLnClass = nsdLnClass74 || nsdLnClass7420; + if (!nsdLnClass) return undefined; + + const lnClassJson: LNodeDescription = {}; + getDataObjects(nsdLnClass).forEach((dataObject) => { + const name = dataObject.getAttribute("name")!; + + lnClassJson[name] = nsdDataObject(dataObject); + }); - return lnClassJson; + return lnClassJson + } } diff --git a/tDataTypeTemplates/nsdToJson/testJson/ARIS.ts b/tDataTypeTemplates/nsdToJson/testJson/ARIS.ts index 18d1655..7dc019e 100644 --- a/tDataTypeTemplates/nsdToJson/testJson/ARIS.ts +++ b/tDataTypeTemplates/nsdToJson/testJson/ARIS.ts @@ -1,35 +1,13 @@ -import { acdChildren } from "./ACD.js"; -import { actChildren } from "./ACT.js"; -import { apcChildren } from "./APC.js"; -import { asgChildren } from "./ASG.js"; -import { bacChildren } from "./BAC.js"; -import { bcrChildren } from "./BCR.js"; -import { bscChildren } from "./BSC.js"; import { cmvChildren } from "./CMV.js"; -import { delChildren } from "./DEL.js"; -import { dpcChildren } from "./DPC.js"; -import { dplChildren } from "./DPL.js"; -import { hdelChildren } from "./HDEL.js"; -import { hmvChildren } from "./HMV.js"; -import { hstChildren } from "./HST.js"; -import { hwyeChildren } from "./HWYE.js"; import { incChildren } from "./INC.js"; import { ingChildren } from "./ING.js"; import { insChildren } from "./INS.js"; import { lplChildren } from "./LPL.js"; import { mvChildren } from "./MV.js"; import { orgChildren } from "./ORG.js"; -import { orsChildren } from "./ORS.js"; -import { savChildren } from "./SAV.js"; -import { secChildren } from "./SEC.js"; -import { seqChildren } from "./SEQ.js"; import { spcChildren } from "./SPC.js"; import { spsChildren } from "./SPS.js"; -import { tscChildren } from "./TSC.js"; -import { tsgChildren } from "./TSG.js"; -import { vsgChildren } from "./VSG.js"; -import { vssChildren } from "./VSS.js"; -import { wyeChildren } from "./WYE.js"; + export const aris = { Auto: { tagName: "DataObject", diff --git a/tDataTypeTemplates/nsdToJson/testJson/FSCH.ts b/tDataTypeTemplates/nsdToJson/testJson/FSCH.ts index 5b85260..b82b500 100644 --- a/tDataTypeTemplates/nsdToJson/testJson/FSCH.ts +++ b/tDataTypeTemplates/nsdToJson/testJson/FSCH.ts @@ -7,7 +7,7 @@ import { orgChildren } from "./ORG.js"; import { spcChildren } from "./SPC.js"; import { spsChildren } from "./SPS.js"; import { spgChildren } from "./SPG.js"; -import { tscChildren } from "./TSC.js"; +import { tcsChildren } from "./TCS.js"; import { tsgChildren } from "./TSG.js"; export const fsch = { @@ -1551,7 +1551,7 @@ export const fsch = { descID: "IEC61850_7_4.LNGroupF::FSCH.ActStrTm.desc", presCond: "O", dsPresCond: "na", - children: tscChildren, + children: tcsChildren, }, NxtStrTm: { tagName: "DataObject", @@ -1561,7 +1561,7 @@ export const fsch = { mandatory: true, presCond: "M", dsPresCond: "na", - children: tscChildren, + children: tcsChildren, }, SchdEnaErr: { tagName: "DataObject", diff --git a/tDataTypeTemplates/nsdToJson/testJson/IARC.ts b/tDataTypeTemplates/nsdToJson/testJson/IARC.ts index aa9dc35..c047075 100644 --- a/tDataTypeTemplates/nsdToJson/testJson/IARC.ts +++ b/tDataTypeTemplates/nsdToJson/testJson/IARC.ts @@ -1,35 +1,11 @@ -import { acdChildren } from "./ACD.js"; -import { actChildren } from "./ACT.js"; -import { apcChildren } from "./APC.js"; -import { asgChildren } from "./ASG.js"; -import { bacChildren } from "./BAC.js"; -import { bcrChildren } from "./BCR.js"; -import { bscChildren } from "./BSC.js"; -import { cmvChildren } from "./CMV.js"; -import { delChildren } from "./DEL.js"; -import { dpcChildren } from "./DPC.js"; import { dplChildren } from "./DPL.js"; -import { hdelChildren } from "./HDEL.js"; -import { hmvChildren } from "./HMV.js"; -import { hstChildren } from "./HST.js"; -import { hwyeChildren } from "./HWYE.js"; -import { incChildren } from "./INC.js"; import { ingChildren } from "./ING.js"; import { insChildren } from "./INS.js"; import { lplChildren } from "./LPL.js"; -import { mvChildren } from "./MV.js"; import { orgChildren } from "./ORG.js"; -import { orsChildren } from "./ORS.js"; -import { savChildren } from "./SAV.js"; -import { secChildren } from "./SEC.js"; -import { seqChildren } from "./SEQ.js"; import { spcChildren } from "./SPC.js"; import { spsChildren } from "./SPS.js"; -import { tscChildren } from "./TSC.js"; -import { tsgChildren } from "./TSG.js"; -import { vsgChildren } from "./VSG.js"; -import { vssChildren } from "./VSS.js"; -import { wyeChildren } from "./WYE.js"; + export const iarc = { EEName: { tagName: "DataObject", diff --git a/tDataTypeTemplates/nsdToJson/testJson/PTOF.ts b/tDataTypeTemplates/nsdToJson/testJson/PTOF.ts index 5eec1a1..7fc4e1d 100644 --- a/tDataTypeTemplates/nsdToJson/testJson/PTOF.ts +++ b/tDataTypeTemplates/nsdToJson/testJson/PTOF.ts @@ -1,35 +1,15 @@ import { acdChildren } from "./ACD.js"; import { actChildren } from "./ACT.js"; -import { apcChildren } from "./APC.js"; import { asgChildren } from "./ASG.js"; -import { bacChildren } from "./BAC.js"; -import { bcrChildren } from "./BCR.js"; -import { bscChildren } from "./BSC.js"; import { cmvChildren } from "./CMV.js"; -import { delChildren } from "./DEL.js"; -import { dpcChildren } from "./DPC.js"; -import { dplChildren } from "./DPL.js"; -import { hdelChildren } from "./HDEL.js"; -import { hmvChildren } from "./HMV.js"; -import { hstChildren } from "./HST.js"; -import { hwyeChildren } from "./HWYE.js"; import { incChildren } from "./INC.js"; import { ingChildren } from "./ING.js"; import { insChildren } from "./INS.js"; import { lplChildren } from "./LPL.js"; -import { mvChildren } from "./MV.js"; import { orgChildren } from "./ORG.js"; -import { orsChildren } from "./ORS.js"; -import { savChildren } from "./SAV.js"; -import { secChildren } from "./SEC.js"; -import { seqChildren } from "./SEQ.js"; import { spcChildren } from "./SPC.js"; import { spsChildren } from "./SPS.js"; -import { tscChildren } from "./TSC.js"; -import { tsgChildren } from "./TSG.js"; -import { vsgChildren } from "./VSG.js"; -import { vssChildren } from "./VSS.js"; -import { wyeChildren } from "./WYE.js"; + export const ptof = { Str: { tagName: "DataObject", diff --git a/tDataTypeTemplates/nsdToJson/testJson/TSC.ts b/tDataTypeTemplates/nsdToJson/testJson/TCS.ts similarity index 97% rename from tDataTypeTemplates/nsdToJson/testJson/TSC.ts rename to tDataTypeTemplates/nsdToJson/testJson/TCS.ts index 2d0a59e..6e47437 100644 --- a/tDataTypeTemplates/nsdToJson/testJson/TSC.ts +++ b/tDataTypeTemplates/nsdToJson/testJson/TCS.ts @@ -1,4 +1,4 @@ -export const tscChildren = { +export const tcsChildren = { stVal: { tagName: "DataAttribute", name: "stVal", diff --git a/tDataTypeTemplates/nsdToJson/testNsdJson.ts b/tDataTypeTemplates/nsdToJson/testNsdJson.ts index e9ce39b..7a99e33 100644 --- a/tDataTypeTemplates/nsdToJson/testNsdJson.ts +++ b/tDataTypeTemplates/nsdToJson/testNsdJson.ts @@ -178,6 +178,39 @@ import { zsmc } from "./testJson/ZSMC.js"; import { zscr } from "./testJson/ZSCR.js"; import { ztcf } from "./testJson/ZTCF.js"; import { ztcr } from "./testJson/ZTCR.js"; +import { acdChildren } from "./testJson/ACD.js"; +import { actChildren } from "./testJson/ACT.js"; +import { apcChildren } from "./testJson/APC.js"; +import { asgChildren } from "./testJson/ASG.js"; +import { bacChildren } from "./testJson/BAC.js"; +import { bcrChildren } from "./testJson/BCR.js"; +import { bscChildren } from "./testJson/BSC.js"; +import { cmvChildren } from "./testJson/CMV.js"; +import { delChildren } from "./testJson/DEL.js"; +import { dpcChildren } from "./testJson/DPC.js"; +import { dplChildren } from "./testJson/DPL.js"; +import { hdelChildren } from "./testJson/HDEL.js"; +import { hmvChildren } from "./testJson/HMV.js"; +import { hstChildren } from "./testJson/HST.js"; +import { hwyeChildren } from "./testJson/HWYE.js"; +import { incChildren } from "./testJson/INC.js"; +import { iscChildren } from "./testJson/ISC.js"; +import { insChildren } from "./testJson/INS.js"; +import { lplChildren } from "./testJson/LPL.js"; +import { mvChildren } from "./testJson/MV.js"; +import { orgChildren } from "./testJson/ORG.js"; +import { orsChildren } from "./testJson/ORS.js"; +import { savChildren } from "./testJson/SAV.js"; +import { secChildren } from "./testJson/SEC.js"; +import { seqChildren } from "./testJson/SEQ.js"; +import { spcChildren } from "./testJson/SPC.js"; +import { spgChildren } from "./testJson/SPG.js"; +import { spsChildren } from "./testJson/SPS.js"; +import { tcsChildren } from "./testJson/TCS.js"; +import { tsgChildren } from "./testJson/TSG.js"; +import { vsgChildren } from "./testJson/VSG.js"; +import { vssChildren } from "./testJson/VSS.js"; +import { wyeChildren } from "./testJson/WYE.js"; export const lnClass74 = [ "ANCR", @@ -514,3 +547,39 @@ export const lnClassData: Record = { ZTCF: ztcf, ZTCR: ztcr, }; + +export const cdcData: Record = { + "ACD":acdChildren, + "ACT":actChildren, + "APC":apcChildren, + "ASG":asgChildren, + "BAC":bacChildren, + "BCR":bcrChildren, + "BSC":bscChildren, + "CMV":cmvChildren, + "DEL":delChildren, + "DPC":dpcChildren, + "DPL":dplChildren, + "HDEL":hdelChildren, + "HMV":hmvChildren, + "HST":hstChildren, + "HWYE":hwyeChildren, + "INC":incChildren, + "INS":insChildren, + "ISC":iscChildren, + "LPL":lplChildren, + "MV":mvChildren, + "ORG":orgChildren, + "ORS":orsChildren, + "SAV":savChildren, + "SEC":secChildren, + "SEQ":seqChildren, + "SPC":spcChildren, + "SPG":spgChildren, + "SPS":spsChildren, + "TCS":tcsChildren, + "TSG":tsgChildren, + "VSG":vsgChildren, + "VSS":vssChildren, + "WYE":wyeChildren +};