diff --git a/index.ts b/index.ts index 4e64511..af3db5d 100644 --- a/index.ts +++ b/index.ts @@ -68,6 +68,8 @@ export { export { sourceControlBlock } from "./tExtRef/sourceControlBlock.js"; export { isSubscribed } from "./tExtRef/isSubscribed.js"; +export { importLNodeType } from "./tDataTypeTemplates/importLNodeType.js"; + export { Supervision, SupervisionOptions, diff --git a/package-lock.json b/package-lock.json index a8d42a4..3bfa94d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@openenergytools/scl-lib", - "version": "0.13.3", + "version": "0.14.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@openenergytools/scl-lib", - "version": "0.13.3", + "version": "0.14.3", "license": "Apache-2.0", "devDependencies": { "@openscd/open-scd-core": "^0.0.2", diff --git a/package.json b/package.json index e664c1a..3c9877e 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "Function library supporting SCL editing", "license": "Apache-2.0", "author": "Jakob Vogelsang", - "version": "0.13.3", + "version": "0.14.3", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", diff --git a/tDataTypeTemplates/importLNodeType.spec.ts b/tDataTypeTemplates/importLNodeType.spec.ts new file mode 100644 index 0000000..682c733 --- /dev/null +++ b/tDataTypeTemplates/importLNodeType.spec.ts @@ -0,0 +1,57 @@ +import { expect } from "chai"; + +import { importLNodeType } from "./importLNodeType.js"; +import { + baseDataTypes, + emptyBayTemplate, + competeBayTemplate, + invalidBayTemplate, +} from "./importLNodeType.testfiles.js"; + +import { findElement } from "../foundation/helpers.test.js"; + +const emptyTemplate = findElement(emptyBayTemplate) as XMLDocument; +const completeTemplate = findElement(competeBayTemplate) as XMLDocument; +const invalidTemplate = findElement(invalidBayTemplate) as XMLDocument; +const tctrLNodeType = findElement( + baseDataTypes, + 'LNodeType[id="Dummy.TCTR"]' +) as Element; +const mmxuLNodeType = findElement( + baseDataTypes, + 'LNodeType[id="Dummy.MMXU"]' +) as Element; + +describe("Function to import LNodeType with its sub data", () => { + it("is inserting the LNodeType itself element when missing", () => { + const edits = importLNodeType(tctrLNodeType, emptyTemplate); + + expect(edits.length).to.equal(6); + expect(edits[1].node).to.equal(tctrLNodeType); + }); + + it("is inserting DataTypeTemplate element when missing", () => { + const edits = importLNodeType(tctrLNodeType, emptyTemplate); + + expect(edits.length).to.equal(6); + expect((edits[0].node as Element).tagName).to.equal("DataTypeTemplates"); + }); + + it("can deal with structure DOs and DAs", () => { + const edits = importLNodeType(mmxuLNodeType, completeTemplate); + + expect(edits.length).to.equal(14); + }); + + it("is checking for duplicate data types", () => { + const edits = importLNodeType(tctrLNodeType, completeTemplate); + + expect(edits.length).to.equal(0); + }); + + it("is returning an empty string on invalid SCL files", () => { + const edits = importLNodeType(tctrLNodeType, invalidTemplate); + + expect(edits.length).to.equal(0); + }); +}); diff --git a/tDataTypeTemplates/importLNodeType.testfiles.ts b/tDataTypeTemplates/importLNodeType.testfiles.ts new file mode 100644 index 0000000..43cdf6e --- /dev/null +++ b/tDataTypeTemplates/importLNodeType.testfiles.ts @@ -0,0 +1,348 @@ +export const emptyBayTemplate = `<SCL xmlns="http://www.iec.ch/61850/2003/SCL" xmlns:esld="https://transpower.co.nz/SCL/SSD/SLD/v0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2007" revision="B" release="4"> +<Header id="BayTemplate"/> +<Substation name="S1"> + <VoltageLevel name="V1" desc="" nomFreq="50" numPhases="3"> + <Voltage unit="V" multiplier="k">110</Voltage> + <Bay name="B1" desc=""/> + </VoltageLevel> +</Substation> +</SCL>`; + +export const competeBayTemplate = `<SCL xmlns="http://www.iec.ch/61850/2003/SCL" xmlns:esld="https://transpower.co.nz/SCL/SSD/SLD/v0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2007" revision="B" release="4"> +<Header id="BayTemplate"/> +<Substation name="S1"> + <VoltageLevel name="V1" desc="" nomFreq="50" numPhases="3"> + <Voltage unit="V" multiplier="k">110</Voltage> + <Bay name="B1" desc=""/> + </VoltageLevel> +</Substation> +<DataTypeTemplates> + <LNodeType lnClass="TCTR" id="Dummy.TCTR" desc="Current Transformer"> + <DO name="Beh" desc="ENS_Beh" type="OpenSCD_ENS_Beh"/> + <DO name="AmpSv" type="Dummy.SAV"/> + </LNodeType> + <DOType cdc="ENS" id="OpenSCD_ENS_Beh"> + <DA name="stVal" bType="Enum" dchg="true" fc="ST" type="BehaviourModeKind"/> + <DA name="q" bType="Quality" qchg="true" fc="ST"/> + <DA name="t" bType="Timestamp" fc="ST"/> + </DOType> + <DOType cdc="SAV" id="Dummy.SAV" desc="Sampled value"> + <DA fc="MX" name="instMag" bType="Struct" type="someAnalogueValueINT32"/> + <DA fc="MX" qchg="true" name="q" bType="Quality"/> + </DOType> + <DAType id="someAnalogueValueINT32"> + <BDA name="i" bType="INT32" /> + </DAType> + <EnumType id="BehaviourModeKind"> + <EnumVal ord="1">on</EnumVal> + <EnumVal ord="2">blocked</EnumVal> + <EnumVal ord="3">test</EnumVal> + <EnumVal ord="4">test/blocked</EnumVal> + <EnumVal ord="5">off</EnumVal> + </EnumType> +</DataTypeTemplates> +</SCL>`; +export const invalidBayTemplate = `<SomeInvalidSCL></SomeInvalidSCL>`; + +export const baseDataTypes = ` +<SCL xmlns="http://www.iec.ch/61850/2003/SCL" version="2007" revision="B" release="4"> +<Header id="BaseDataTypes"/> +<DataTypeTemplates> + <LNodeType lnClass="LGOS" id="Dummy.LGOS"> + <DO name="GoCBRef" type="Dummy.ORG"/> + <DO name="St" type="OpenSCD_SPS_simple"/> + <DO name="Mod" type="OpenSCD_ENC_Mod"/> + <DO name="Health" type="OpenSCD_ENS_Health"/> + <DO name="Beh" type="OpenSCD_ENS_Beh"/> + <DO name="NamPlt" type="OpenSCD_LPL_noLD"/> + </LNodeType> + <LNodeType lnClass="LGOS" id="Dummy.LGOS1"> + <DO name="GoCBRef" type="Dummy.ORG1"/> + <DO name="SomethingLikeGoCBRef" type="Dummy.ORG1"/> + <DO name="St" type="OpenSCD_SPS_simple"/> + <DO name="Mod" type="OpenSCD_ENC_Mod"/> + <DO name="Health" type="OpenSCD_ENS_Health"/> + <DO name="Beh" type="OpenSCD_ENS_Beh"/> + <DO name="NamPlt" type="OpenSCD_LPL_noLD"/> + </LNodeType> + <LNodeType lnClass="LGOS" id="Dummy.LGOS2"> + <DO name="GoCBRef" type="Dummy.ORG2"/> + <DO name="St" type="OpenSCD_SPS_simple"/> + <DO name="Mod" type="OpenSCD_ENC_Mod"/> + <DO name="Health" type="OpenSCD_ENS_Health"/> + <DO name="Beh" type="OpenSCD_ENS_Beh"/> + <DO name="NamPlt" type="OpenSCD_LPL_noLD"/> + </LNodeType> + <LNodeType lnClass="LSVS" id="Dummy.LSVS"> + <DO name="SvCBRef" type="Dummy.ORG"/> + <DO name="St" type="OpenSCD_SPS_simple"/> + <DO name="Mod" type="OpenSCD_ENC_Mod"/> + <DO name="Health" type="OpenSCD_ENS_Health"/> + <DO name="Beh" type="OpenSCD_ENS_Beh"/> + <DO name="NamPlt" type="OpenSCD_LPL_noLD"/> + </LNodeType> + <LNodeType lnClass="XSWI" id="Dummy.XSWI" desc="Switch: one phase represenation"> + <DO name="Mod" type="OpenSCD_ENC_Mod"/> + <DO name="Beh" type="OpenSCD_ENS_Beh"/> + <DO name="Health" type="OpenSCD_ENS_Health"/> + <DO name="NamPlt" type="OpenSCD_LPL_noLD"/> + <DO name="LocKey" type="OpenSCD_SPS_simple"/> + <DO name="Loc" type="OpenSCD_SPS_simple"/> + <DO name="OpCnt" type="OpenSCD_INS_simple"/> + <DO name="Pos" type="OpenSCD_DPC_statusonly"/> + <DO name="BlkOpn" type="OpenSCD_SPC_statusonly"/> + <DO name="BlkCls" type="OpenSCD_SPC_statusonly"/> + <DO name="SwTyp" type="OpenSCD_ENS_SwTyp"/> + </LNodeType> + <LNodeType lnClass="CSWI" id="Dummy.CSWI" desc="Switch control: no process bus(PB)"> + <DO name="Mod" type="OpenSCD_ENC_Mod"/> + <DO name="Beh" type="OpenSCD_ENS_Beh"/> + <DO name="Health" type="OpenSCD_ENS_Health"/> + <DO name="NamPlt" type="OpenSCD_LPL_noLD"/> + <DO name="LocKey" type="OpenSCD_SPS_simple"/> + <DO name="Loc" type="OpenSCD_SPS_simple"/> + <DO name="Pos" type="OpenSCD_DPC"/> + </LNodeType> + <LNodeType lnClass="CILO" id="Dummy.CILO" desc="Interlocking"> + <DO name="Mod" type="OpenSCD_ENC_Mod"/> + <DO name="Beh" type="OpenSCD_ENS_Beh"/> + <DO name="Health" type="OpenSCD_ENS_Health"/> + <DO name="NamPlt" type="OpenSCD_LPL_noLD"/> + <DO name="EnaOpn" type="OpenSCD_SPS_simple"/> + <DO name="EnaCls" type="OpenSCD_SPS_simple"/> + </LNodeType> + <LNodeType lnClass="LLN0" id="Dummy.LLN0" desc="Logical device LN: parent"> + <DO name="Mod" type="OpenSCD_ENC_Mod"/> + <DO name="Beh" type="OpenSCD_ENS_Beh"/> + <DO name="Health" type="OpenSCD_ENS_Health"/> + <DO name="NamPlt" type="OpenSCD_LPL_LD"/> + <DO name="LocKey" type="OpenSCD_SPS_simple"/> + <DO name="Loc" type="OpenSCD_SPS_simple"/> + </LNodeType> + <LNodeType lnClass="MMXU" id="Dummy.MMXU" desc="Logical device LN: parent"> + <DO name="Mod" type="OpenSCD_ENC_Mod"/> + <DO name="Beh" type="OpenSCD_ENS_Beh"/> + <DO name="Health" type="OpenSCD_ENS_Health"/> + <DO name="NamPlt" type="OpenSCD_LPL_LD"/> + <DO name="A" type="someWYE"/> + </LNodeType> + <LNodeType lnClass="TCTR" id="Dummy.TCTR" desc="Current Transformer"> + <DO name="Beh" desc="ENS_Beh" type="OpenSCD_ENS_Beh"/> + <DO name="AmpSv" type="Dummy.SAV"/> + </LNodeType> + <DOType cdc="SAV" id="Dummy.SAV" desc="Sampled value"> + <DA fc="MX" name="instMag" bType="Struct" type="someAnalogueValueINT32"/> + <DA fc="MX" qchg="true" name="q" bType="Quality"/> + </DOType> + <DOType cdc="WYE" id="someWYE"> + <SDO name="phsA" type="someMV"/> + </DOType> + <DOType cdc="CMV" id="someMV"> + <DA fc="MX" dchg="true" name="cVal" bType="Struct" type="someVector"/> + </DOType> + <DOType cdc="ORG" id="Dummy.ORG"> + <DA name="setSrcRef" bType="ObjRef" dchg="true" valKind="RO" valImport="true" fc="SP"/> + </DOType> + <DOType cdc="ORG" id="Dummy.ORG1"> + <DA name="setSrcRef" bType="ObjRef" dchg="true" valKind="Conf" valImport="true" fc="SP"/> + </DOType> + <DOType cdc="ORG" id="Dummy.ORG2"> + <DA name="setSrcRef" bType="ObjRef" dchg="true" fc="SP"/> + </DOType> + <DOType cdc="ENS" id="OpenSCD_ENS_SwTyp"> + <DA fc="ST" dchg="true" name="stVal" bType="Enum" type="SwitchFunctionKind"/> + <DA fc="ST" qchg="true" name="q" bType="Quality"/> + <DA fc="ST" name="t" bType="Timestamp"/> + </DOType> + <DOType cdc="SPC" id="OpenSCD_SPC_statusonly"> + <DA name="stVal" bType="BOOLEAN" dchg="true" fc="ST"/> + <DA name="q" bType="Quality" qchg="true" fc="ST"/> + <DA name="t" bType="Timestamp" fc="ST"/> + <DA name="ctlModel" bType="Enum" dchg="true" type="OpenSCD_StatusOnly" fc="CF"> + <Val>status-only</Val> + </DA> + </DOType> + <DOType cdc="DPC" id="OpenSCD_DPC_statusonly"> + <DA name="stVal" bType="Dbpos" dchg="true" fc="ST"/> + <DA name="q" bType="Quality" qchg="true" fc="ST"/> + <DA name="t" bType="Timestamp" fc="ST"/> + <DA name="ctlModel" bType="Enum" fc="CF" type="OpenSCD_StatusOnly"> + <Val>status-only</Val> + </DA> + </DOType> + <DOType cdc="INS" id="OpenSCD_INS_simple"> + <DA name="stVal" bType="INT32" dchg="true" fc="ST"/> + <DA name="q" bType="Quality" qchg="true" fc="ST"/> + <DA name="t" bType="Timestamp" fc="ST"/> + <DA name="d" bType="VisString255" fc="DC"/> + </DOType> + <DOType cdc="DPC" id="OpenSCD_DPC"> + <DA name="origin" bType="Struct" dchg="true" fc="ST" type="OpenSCD_Originator"/> + <DA name="stVal" bType="Dbpos" dchg="true" fc="ST"/> + <DA name="q" bType="Quality" qchg="true" fc="ST"/> + <DA name="t" bType="Timestamp" fc="ST"/> + <DA name="ctlModel" bType="Enum" fc="CF" type="CtlModelKind"> + <Val>sbo-with-enhanced-security</Val> + </DA> + <DA name="sboTimeout" bType="INT32U" fc="CF"> + <Val>30000</Val> + </DA> + <DA name="operTimeout" bType="INT32U" fc="CF"> + <Val>600</Val> + </DA> + <DA name="pulseConfig" bType="Struct" fc="CO" type="OpenSCD_PulseConfig"/> + <DA name="SBOw" bType="Struct" fc="CO" type="OpenSCD_OperSBOw_Dbpos"/> + <DA name="Oper" bType="Struct" fc="CO" type="OpenSCD_OperSBOw_Dbpos"/> + <DA name="Cancel" bType="Struct" fc="CO" type="OpenSCD_Cancel_Dbpos"/> + </DOType> + <DOType cdc="LPL" id="OpenSCD_LPL_noLD"> + <DA name="vendor" bType="VisString255" fc="DC"/> + <DA name="swRev" bType="VisString255" fc="DC"/> + <DA name="d" bType="VisString255" fc="DC"/> + <DA name="configRev" bType="VisString255" fc="DC"/> + </DOType> + <DOType cdc="SPS" id="OpenSCD_SPS_simple"> + <DA name="stVal" bType="BOOLEAN" dchg="true" fc="ST"/> + <DA name="q" bType="Quality" qchg="true" fc="ST"/> + <DA name="t" bType="Timestamp" fc="ST"/> + <DA name="d" bType="VisString255" fc="DC"/> + </DOType> + <DOType cdc="LPL" id="OpenSCD_LPL_LD"> + <DA name="vendor" bType="VisString255" fc="DC"/> + <DA name="swRev" bType="VisString255" fc="DC"/> + <DA name="d" bType="VisString255" fc="DC"/> + <DA name="configRev" bType="VisString255" fc="DC"/> + <DA name="ldNs" bType="VisString255" fc="EX"> + <Val>IEC 61850-7-4:2007B4</Val> + </DA> + </DOType> + <DOType cdc="ENS" id="OpenSCD_ENS_Health"> + <DA name="stVal" bType="Enum" dchg="true" fc="ST" type="HealthKind"/> + <DA name="q" bType="Quality" qchg="true" fc="ST"/> + <DA name="t" bType="Timestamp" fc="ST"/> + </DOType> + <DOType cdc="ENS" id="OpenSCD_ENS_Beh"> + <DA name="stVal" bType="Enum" dchg="true" fc="ST" type="BehaviourModeKind"/> + <DA name="q" bType="Quality" qchg="true" fc="ST"/> + <DA name="t" bType="Timestamp" fc="ST"/> + </DOType> + <DOType cdc="ENC" id="OpenSCD_ENC_Mod"> + <DA name="origin" bType="Struct" dchg="true" fc="ST" type="OpenSCD_Originator"/> + <DA name="stVal" bType="Enum" dchg="true" fc="ST" type="BehaviourModeKind"/> + <DA name="q" bType="Quality" qchg="true" fc="ST"/> + <DA name="t" bType="Timestamp" fc="ST"/> + <DA name="ctlModel" bType="Enum" fc="CF" type="CtlModelKind"> + <Val>sbo-with-enhanced-security</Val> + </DA> + <DA name="sboTimeout" bType="INT32U" fc="CF"> + <Val>30000</Val> + </DA> + <DA name="operTimeout" bType="INT32U" fc="CF"> + <Val>600</Val> + </DA> + <DA name="SBOw" bType="Struct" fc="CO" type="OpenSCD_OperSBOw_BehaviourModeKind"/> + <DA name="Oper" bType="Struct" fc="CO" type="OpenSCD_OperSBOw_BehaviourModeKind"/> + <DA name="Cancel" bType="Struct" fc="CO" type="OpenSCD_Cancel_BehaviourModeKind"/> + </DOType> + <DAType id="someVector"> + <BDA name="mag" bType="Struct" type="someAnalogueValue"/> + <BDA name="ang" bType="Struct" type="someAnalogueValue"/> + </DAType> + <DAType id="someAnalogueValue"> + <BDA name="f" bType="FLOAT32" /> + </DAType> + <DAType id="someAnalogueValueINT32"> + <BDA name="i" bType="INT32" /> + </DAType> + <DAType id="OpenSCD_Cancel_Dbpos"> + <BDA name="ctlVal" bType="Dbpos"/> + <BDA name="origin" bType="Struct" type="OpenSCD_Originator"/> + <BDA name="ctlNum" bType="INT8U"/> + <BDA name="T" bType="Timestamp"/> + <BDA name="Test" bType="BOOLEAN"/> + <ProtNs type="8-MMS">IEC 61850-8-1:2003</ProtNs> + </DAType> + <DAType id="OpenSCD_OperSBOw_Dbpos"> + <BDA name="ctlVal" bType="Dbpos"/> + <BDA name="origin" bType="Struct" type="OpenSCD_Originator"/> + <BDA name="ctlNum" bType="INT8U"/> + <BDA name="T" bType="Timestamp"/> + <BDA name="Test" bType="BOOLEAN"/> + <BDA name="Check" bType="Check"/> + <ProtNs type="8-MMS">IEC 61850-8-1:2003</ProtNs> + </DAType> + <DAType id="OpenSCD_PulseConfig"> + <BDA name="cmdQual" bType="Enum" type="OutputSignalKind"/> + <BDA name="onDur" bType="INT32U"/> + <BDA name="offDur" bType="INT32U"/> + <BDA name="numPls" bType="INT32U"/> + </DAType> + <DAType id="OpenSCD_Cancel_BehaviourModeKind"> + <BDA name="ctlVal" bType="Enum" type="BehaviourModeKind"/> + <BDA name="origin" bType="Struct" type="OpenSCD_Originator"/> + <BDA name="ctlNum" bType="INT8U"/> + <BDA name="T" bType="Timestamp"/> + <BDA name="Test" bType="BOOLEAN"/> + <ProtNs type="8-MMS">IEC 61850-8-1:2003</ProtNs> + </DAType> + <DAType id="OpenSCD_OperSBOw_BehaviourModeKind"> + <BDA name="ctlVal" bType="Enum" type="BehaviourModeKind"/> + <BDA name="origin" bType="Struct" type="OpenSCD_Originator"/> + <BDA name="ctlNum" bType="INT8U"/> + <BDA name="T" bType="Timestamp"/> + <BDA name="Test" bType="BOOLEAN"/> + <BDA name="Check" bType="Check"/> + <ProtNs type="8-MMS">IEC 61850-8-1:2003</ProtNs> + </DAType> + <DAType id="OpenSCD_Originator"> + <BDA name="orCat" bType="Enum" type="OriginatorCategoryKind"/> + <BDA name="orIdent" bType="Octet64"/> + </DAType> + <EnumType id="SwitchFunctionKind"> + <EnumVal ord="1">Load Break</EnumVal> + <EnumVal ord="2">Disconnector</EnumVal> + <EnumVal ord="3">Earthing Switch</EnumVal> + <EnumVal ord="4">High Speed Earthing Switch</EnumVal> + </EnumType> + <EnumType id="OpenSCD_StatusOnly"> + <EnumVal ord="0">status-only</EnumVal> + </EnumType> + <EnumType id="OutputSignalKind"> + <EnumVal ord="0">pulse</EnumVal> + <EnumVal ord="1">persistent</EnumVal> + <EnumVal ord="2">persistent-feedback</EnumVal> + </EnumType> + <EnumType id="HealthKind"> + <EnumVal ord="1">Ok</EnumVal> + <EnumVal ord="2">Warning</EnumVal> + <EnumVal ord="3">Alarm</EnumVal> + </EnumType> + <EnumType id="CtlModelKind"> + <EnumVal ord="0">status-only</EnumVal> + <EnumVal ord="1">direct-with-normal-security</EnumVal> + <EnumVal ord="2">sbo-with-normal-security</EnumVal> + <EnumVal ord="3">direct-with-enhanced-security</EnumVal> + <EnumVal ord="4">sbo-with-enhanced-security</EnumVal> + </EnumType> + <EnumType id="BehaviourModeKind"> + <EnumVal ord="1">on</EnumVal> + <EnumVal ord="2">blocked</EnumVal> + <EnumVal ord="3">test</EnumVal> + <EnumVal ord="4">test/blocked</EnumVal> + <EnumVal ord="5">off</EnumVal> + </EnumType> + <EnumType id="OriginatorCategoryKind"> + <EnumVal ord="0">not-supported</EnumVal> + <EnumVal ord="1">bay-control</EnumVal> + <EnumVal ord="2">station-control</EnumVal> + <EnumVal ord="3">remote-control</EnumVal> + <EnumVal ord="4">automatic-bay</EnumVal> + <EnumVal ord="5">automatic-station</EnumVal> + <EnumVal ord="6">automatic-remote</EnumVal> + <EnumVal ord="7">maintenance</EnumVal> + <EnumVal ord="8">process</EnumVal> + </EnumType> +</DataTypeTemplates> +</SCL> + +`; diff --git a/tDataTypeTemplates/importLNodeType.ts b/tDataTypeTemplates/importLNodeType.ts new file mode 100644 index 0000000..b96f738 --- /dev/null +++ b/tDataTypeTemplates/importLNodeType.ts @@ -0,0 +1,137 @@ +import { Insert, createElement } from "../foundation/utils.js"; + +import { getReference } from "../tBaseElement/getReference.js"; + +function removeDuplicates(inserts: Insert[]): Insert[] { + const uniqueInserts: Insert[] = []; + const uniqueIDs: string[] = []; + + inserts.forEach((insert) => { + const id = (insert.node as Element).getAttribute("id"); + if (!id) uniqueInserts.push(insert); + else if (!uniqueIDs.includes(id)) { + uniqueIDs.push(id); + uniqueInserts.push(insert); + } + }); + + return uniqueInserts; +} + +function insertDataType( + dataType: Element, + oldDataTypeTemplates: Element +): Insert | undefined { + const existingDataType = oldDataTypeTemplates.querySelector( + `${dataType.tagName}[id="${dataType.getAttribute("id")}"] ` + ); + if (existingDataType && dataType.isEqualNode(existingDataType)) return; + + return { + parent: oldDataTypeTemplates, + node: dataType, + reference: getReference(oldDataTypeTemplates, dataType.tagName), + }; +} + +function insertDataTypes(dataTypes: Element[], targetScl: Element): Insert[] { + const dataTypeEdit: Insert[] = []; + + const targetDataTypeTemplates = targetScl.querySelector( + ":root > DataTypeTemplates" + ) + ? targetScl.querySelector(":root > DataTypeTemplates")! + : createElement(targetScl.ownerDocument, "DataTypeTemplates", {}); + + if (!targetDataTypeTemplates.parentElement) { + dataTypeEdit.push({ + parent: targetScl, + node: targetDataTypeTemplates, + reference: getReference(targetScl, "DataTypeTemplates"), + }); + } + + dataTypeEdit.push( + ...dataTypes + .map((dataType) => insertDataType(dataType, targetDataTypeTemplates)) + .filter((insert) => !!insert) + ); + + return removeDuplicates(dataTypeEdit); +} + +function getDaTypes(parent: Element): Element[] { + const doc = parent.ownerDocument; + + const daTypes = Array.from( + parent.querySelectorAll(":scope > DA, :scope > BDA") + ) + .map((dAorBda) => + doc.querySelector( + `:root > DataTypeTemplates > DAType[id="${dAorBda.getAttribute( + "type" + )}"]` + ) + ) + .filter((daType) => !!daType); + + const sndLvDaTypes = daTypes.flatMap((daType) => getDaTypes(daType)); + + return [...daTypes, ...sndLvDaTypes]; +} + +function getDoTypes(parent: Element): Element[] { + const doc = parent.ownerDocument; + + const doTypes = Array.from( + parent.querySelectorAll(":scope > DO, :scope > SDO") + ) + .map((dOorSdo) => + doc.querySelector( + `:root > DataTypeTemplates > DOType[id="${dOorSdo.getAttribute( + "type" + )}"]` + ) + ) + .filter((doType) => !!doType); + + const sndLvDoTypes = doTypes.flatMap((doType) => getDoTypes(doType)); + + return [...doTypes, ...sndLvDoTypes]; +} + +/** + * Importing `LNodeType` including all its sub data structures `DOType`, + * `DAType` and `EnumType` including a check for duplicate data. + * @param lNodeType the `LNodeType` element to be imported + * @param targetDoc the target XML document to import to + * @returns an edit array inserting data type template + */ +export function importLNodeType( + lNodeType: Element, + targetDoc: XMLDocument +): Insert[] { + const doc = lNodeType.ownerDocument; + const targetScl = targetDoc.querySelector("SCL"); + + if (!targetScl) return []; + + const doTypes = getDoTypes(lNodeType); + + const daTypes = doTypes.flatMap((doType) => getDaTypes(doType)); + + const enumTypes = [...doTypes, ...daTypes].flatMap((doOrDaType) => + Array.from( + doOrDaType.querySelectorAll('BDA[bType="Enum"], DA[bType="Enum"]') + ) + .map((dAorBda) => + doc.querySelector(`EnumType[id="${dAorBda.getAttribute("type")}"]`) + ) + .filter((enumType) => !!enumType) + ); + + return insertDataTypes( + [lNodeType, ...doTypes, ...daTypes, ...enumTypes], + targetScl + ); +}