Skip to content

Commit c420df2

Browse files
danyillJakob Vogelsang
authored andcommitted
fix(tIED): Refactor insertIED to avoid duplicate ConnectedAPs (closes #71)
Also, rename importIED folder to insertIED
1 parent 0d38f64 commit c420df2

File tree

8 files changed

+122
-39
lines changed

8 files changed

+122
-39
lines changed

tIED/insertIED.spec.ts

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,37 +34,37 @@ function findElements(scl: Element, tag: string): Element[] {
3434
}
3535

3636
const emptyScl = (
37-
await fetch("tIED/importIED/emptyproject.scd")
37+
await fetch("tIED/insertIED/emptyproject.scd")
3838
.then((response) => response.text())
3939
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
4040
).querySelector("SCL")!;
4141

4242
const multipleIEDs = (
43-
await fetch("tIED/importIED/multipleieds.scd")
43+
await fetch("tIED/insertIED/multipleieds.scd")
4444
.then((response) => response.text())
4545
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
4646
).querySelector("SCL")!;
4747

4848
const validIed = (
49-
await fetch("tIED/importIED/valid.iid")
49+
await fetch("tIED/insertIED/valid.iid")
5050
.then((response) => response.text())
5151
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
5252
).querySelector(':root > IED[name="TestImportIED"]')!;
5353

5454
const validWithMultipleConnAp = (
55-
await fetch("tIED/importIED/validWithMultiSubnets.iid")
55+
await fetch("tIED/insertIED/validWithMultiSubnets.iid")
5656
.then((response) => response.text())
5757
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
5858
).querySelector(':root > IED[name="TestImportIED"]')!;
5959

6060
const duplicateIED = (
61-
await fetch("tIED/importIED/duplicate.iid")
61+
await fetch("tIED/insertIED/duplicate.iid")
6262
.then((response) => response.text())
6363
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
6464
).querySelector(':root > IED[name="IED3"]')!;
6565

6666
const incompleteIED = (
67-
await fetch("tIED/importIED/incomplete.iid")
67+
await fetch("tIED/insertIED/incomplete.iid")
6868
.then((response) => response.text())
6969
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
7070
).querySelector(':root > IED[name="incompleteIED"]')!;
@@ -88,7 +88,7 @@ describe("Function to an importIED and its referenced elements", () => {
8888

8989
it("adds data type templates elements", async () => {
9090
const emptyScl = (
91-
await fetch("tIED/importIED/emptyproject.scd")
91+
await fetch("tIED/insertIED/emptyproject.scd")
9292
.then((response) => response.text())
9393
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
9494
).querySelector("SCL")!;
@@ -103,9 +103,9 @@ describe("Function to an importIED and its referenced elements", () => {
103103
expect(findElements(emptyScl, "EnumType").length).to.equal(4); // only referenced enumType
104104
});
105105

106-
it("add all missing ConnectedAP and SubNetwork", async () => {
106+
it("adds all missing ConnectedAP and SubNetwork", async () => {
107107
const emptyScl = (
108-
await fetch("tIED/importIED/emptyproject.scd")
108+
await fetch("tIED/insertIED/emptyproject.scd")
109109
.then((response) => response.text())
110110
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
111111
).querySelector("SCL")!;
@@ -118,6 +118,56 @@ describe("Function to an importIED and its referenced elements", () => {
118118
expect(findElements(emptyScl, "ConnectedAP").length).to.equal(4);
119119
});
120120

121+
it("only adds ConnectedAP and SubNetwork if not already present", async () => {
122+
const emptyScl = (
123+
await fetch("tIED/insertIED/emptyproject.scd")
124+
.then((response) => response.text())
125+
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
126+
).querySelector("SCL")!;
127+
128+
// try to import the same IED twice
129+
for (let count = 2; count--; ) {
130+
const multipleIEDs = (
131+
await fetch("tIED/insertIED/multipleieds.scd")
132+
.then((response) => response.text())
133+
.then((str) =>
134+
new DOMParser().parseFromString(str, "application/xml"),
135+
)
136+
).querySelector("SCL")!;
137+
138+
const ied = multipleIEDs.querySelector('IED[name="IED3"]') as Element;
139+
140+
const imports = insertIed(emptyScl, ied);
141+
handleEdit(imports);
142+
143+
expect(findElements(emptyScl, "Communication").length).to.equal(1);
144+
expect(findElements(emptyScl, "SubNetwork").length).to.equal(1);
145+
expect(findElements(emptyScl, "ConnectedAP").length).to.equal(1);
146+
}
147+
});
148+
149+
it("preserves SubNetwork children", async () => {
150+
const emptyScl = (
151+
await fetch("tIED/insertIED/emptyproject.scd")
152+
.then((response) => response.text())
153+
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
154+
).querySelector("SCL")!;
155+
156+
const multipleIEDs = (
157+
await fetch("tIED/insertIED/multipleieds.scd")
158+
.then((response) => response.text())
159+
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
160+
).querySelector("SCL")!;
161+
162+
const ied = multipleIEDs.querySelector('IED[name="IED3"]') as Element;
163+
164+
const imports = insertIed(emptyScl, ied);
165+
handleEdit(imports);
166+
167+
expect(findElements(emptyScl, "Text").length).to.equal(1);
168+
expect(findElements(emptyScl, "BitRate").length).to.equal(1);
169+
});
170+
121171
it("with addCommunicationSection set to false does not add communication elements", async () => {
122172
const imports = insertIed(emptyScl, validIed, {
123173
addCommunicationSection: false,
@@ -139,7 +189,7 @@ describe("Function to an importIED and its referenced elements", () => {
139189

140190
it("skips existing SubNetwork element", async () => {
141191
const multipleIEDs = (
142-
await fetch("tIED/importIED/multipleieds.scd")
192+
await fetch("tIED/insertIED/multipleieds.scd")
143193
.then((response) => response.text())
144194
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
145195
).querySelector("SCL")!;
@@ -154,13 +204,13 @@ describe("Function to an importIED and its referenced elements", () => {
154204

155205
it("make sure to follow the schema definitions sequence", async () => {
156206
const scl = (
157-
await fetch("tIED/importIED/multipleieds.scd")
207+
await fetch("tIED/insertIED/multipleieds.scd")
158208
.then((response) => response.text())
159209
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
160210
).querySelector("SCL")!;
161211

162212
const validIed = (
163-
await fetch("tIED/importIED/valid.iid")
213+
await fetch("tIED/insertIED/valid.iid")
164214
.then((response) => response.text())
165215
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
166216
).querySelector(':root > IED[name="TestImportIED"]')!;
@@ -182,13 +232,13 @@ describe("Function to an importIED and its referenced elements", () => {
182232

183233
it("make sure to follow the schema definitions sequence with missing references", async () => {
184234
const scl = (
185-
await fetch("tIED/importIED/emptyproject.scd")
235+
await fetch("tIED/insertIED/emptyproject.scd")
186236
.then((response) => response.text())
187237
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
188238
).querySelector("SCL")!;
189239

190240
const validIed = (
191-
await fetch("tIED/importIED/valid.iid")
241+
await fetch("tIED/insertIED/valid.iid")
192242
.then((response) => response.text())
193243
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
194244
).querySelector(':root > IED[name="TestImportIED"]')!;
@@ -210,13 +260,13 @@ describe("Function to an importIED and its referenced elements", () => {
210260

211261
it("make sure lnType and type reference are not broken", async () => {
212262
const scl1 = (
213-
await fetch("tIED/importIED/multipleieds.scd")
263+
await fetch("tIED/insertIED/multipleieds.scd")
214264
.then((response) => response.text())
215265
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
216266
).querySelector("SCL")!;
217267

218268
const validIed1 = (
219-
await fetch("tIED/importIED/valid.iid")
269+
await fetch("tIED/insertIED/valid.iid")
220270
.then((response) => response.text())
221271
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
222272
).querySelector(':root > IED[name="TestImportIED"]')!;
@@ -247,13 +297,13 @@ describe("Function to an importIED and its referenced elements", () => {
247297

248298
it("make sure no orphan data types are generated", async () => {
249299
const scl2 = (
250-
await fetch("tIED/importIED/multipleieds.scd")
300+
await fetch("tIED/insertIED/multipleieds.scd")
251301
.then((response) => response.text())
252302
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
253303
).querySelector("SCL")!;
254304

255305
const validIed2 = (
256-
await fetch("tIED/importIED/valid.iid")
306+
await fetch("tIED/insertIED/valid.iid")
257307
.then((response) => response.text())
258308
.then((str) => new DOMParser().parseFromString(str, "application/xml"))
259309
).querySelector(':root > IED[name="TestImportIED"]')!;

tIED/insertIED.ts

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,23 @@ export type InsertIedOptions = {
66
addCommunicationSection: boolean;
77
};
88

9+
/**
10+
* Copies an SCL SubNetwork element but without its ConnectedAP children.
11+
* @param subNetwork - SCL SubNetwork element.
12+
* @returns cloned SubNetwork without Element children.
13+
*/
14+
function getNewSubNetwork(subNetwork: Element): Element {
15+
const newSubNetwork = subNetwork.cloneNode(true) as Element;
16+
newSubNetwork.childNodes.forEach((childNode) => {
17+
if (
18+
childNode.nodeType === Node.ELEMENT_NODE &&
19+
childNode.nodeName === "ConnectedAP"
20+
)
21+
newSubNetwork.removeChild(childNode);
22+
});
23+
return newSubNetwork;
24+
}
25+
926
function addCommunicationElements(newIed: Element, scl: Element): Insert[] {
1027
const edits: Insert[] = [];
1128

@@ -22,41 +39,55 @@ function addCommunicationElements(newIed: Element, scl: Element): Insert[] {
2239
reference: getReference(scl, "Communication"),
2340
});
2441

25-
const newSubNetworks = Array.from(
26-
newIed.ownerDocument.querySelectorAll(`:root > Communication > SubNetwork`),
42+
const subNetworks = Array.from(
43+
newIed.ownerDocument.querySelectorAll(":root > Communication > SubNetwork"),
44+
).filter((subNetwork) =>
45+
subNetwork.querySelector(
46+
`:scope > ConnectedAP[iedName="${newIed.getAttribute("name")}"]`,
47+
),
2748
);
2849

29-
newSubNetworks.forEach((newSubNetwork) => {
30-
const subNetworkName = newSubNetwork.getAttribute("name");
31-
// check if subnetwork already exists
50+
subNetworks.forEach((subNetwork) => {
51+
const connectedAps = Array.from(
52+
subNetwork.querySelectorAll(
53+
`:scope > ConnectedAP[iedName="${newIed.getAttribute("name")}"]`,
54+
),
55+
);
56+
3257
const existingSubNetwork = communication.querySelector(
33-
`:root > Communication > SubNetwork[name="${subNetworkName}"]`,
58+
`:root > Communication > SubNetwork[name="${subNetwork?.getAttribute(
59+
"name",
60+
)}"]`,
3461
);
3562

36-
if (!existingSubNetwork) {
37-
// subnetwork is new and can be copied as is
38-
const subNetwork = <Element>newSubNetwork.cloneNode(true);
63+
const usedSubNetwork = existingSubNetwork
64+
? existingSubNetwork
65+
: getNewSubNetwork(subNetwork);
66+
67+
if (!existingSubNetwork)
3968
edits.push({
4069
parent: communication,
41-
node: subNetwork,
70+
node: usedSubNetwork,
4271
reference: getReference(communication, "SubNetwork"),
4372
});
44-
} else {
45-
// subnetwork exists and individual ConnectedAP are copied
46-
const newConnectedAPs = newIed.ownerDocument.querySelectorAll(
47-
`:root > Communication > SubNetwork[name="${subNetworkName}"]
48-
> ConnectedAP[iedName="${newIed.getAttribute("name")}"]`,
73+
74+
connectedAps.forEach((connectedAp) => {
75+
const iedName = newIed.getAttribute("name")!;
76+
const apName = connectedAp.getAttribute("apName")!;
77+
78+
const existingConnectedAp = existingSubNetwork?.querySelector(
79+
`:scope > ConnectedAP[iedName="${iedName}"][apName="${apName}"]`,
4980
);
5081

51-
newConnectedAPs.forEach((newConnectedAP) => {
52-
const connectedAP = <Element>newConnectedAP.cloneNode(true);
82+
if (!existingConnectedAp) {
83+
const connectedAP = <Element>connectedAp.cloneNode(true);
5384
edits.push({
54-
parent: existingSubNetwork,
85+
parent: usedSubNetwork,
5586
node: connectedAP,
56-
reference: getReference(existingSubNetwork, "ConnectedAP"),
87+
reference: getReference(usedSubNetwork, "ConnectedAP"),
5788
});
58-
});
59-
}
89+
}
90+
});
6091
});
6192

6293
return edits;
File renamed without changes.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
</ConnectedAP>
5959
</SubNetwork>
6060
<SubNetwork name="ProcessBus" type="8-MMS">
61+
<Text>I'm a text element!</Text>
62+
<BitRate unit="b/s">10</BitRate>
6163
<ConnectedAP iedName="IED2" apName="P1">
6264
<Address>
6365
<P type="IP">192.168.0.112</P>

0 commit comments

Comments
 (0)