Skip to content

Commit 0ce54df

Browse files
danyillJakobVogelsang
authored andcommitted
feat(tIED): update internal object references on IED rename (closes #125)
1 parent df16680 commit 0ce54df

File tree

3 files changed

+287
-4
lines changed

3 files changed

+287
-4
lines changed

tIED/updateIED.spec.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,4 +132,47 @@ describe("Function to an update the IED name attributes and its referenced eleme
132132
).filter((iedName) => iedName.textContent?.startsWith("newIedName"));
133133
expect(after.length).to.equal(4);
134134
});
135+
136+
it("updates object references without checking permissions", () => {
137+
const sclDom = new DOMParser().parseFromString(scl, "application/xml");
138+
const sub2 = sclDom.querySelector('IED[name="Subscriber2"]')!;
139+
140+
const edits = updateIED({
141+
element: sub2,
142+
attributes: { name: "newIedName" },
143+
});
144+
145+
handleEdit(edits);
146+
147+
const after = Array.from(
148+
sclDom.querySelectorAll(
149+
'DOI[name^="InRef"] > DAI[name="setSrcRef"] > Val',
150+
),
151+
).filter((val) => val.textContent?.startsWith("newIedName"));
152+
153+
expect(after.length).to.equal(5);
154+
});
155+
156+
it("updates object references and checks permissions", () => {
157+
const sclDom = new DOMParser().parseFromString(scl, "application/xml");
158+
const sub2 = sclDom.querySelector('IED[name="Subscriber2"]')!;
159+
160+
const edits = updateIED(
161+
{
162+
element: sub2,
163+
attributes: { name: "newIedName" },
164+
},
165+
true,
166+
);
167+
168+
handleEdit(edits);
169+
170+
const after = Array.from(
171+
sclDom.querySelectorAll(
172+
'DOI[name^="InRef"] > DAI[name="setSrcRef"] > Val',
173+
),
174+
).filter((val) => val.textContent?.startsWith("newIedName"));
175+
176+
expect(after.length).to.equal(4);
177+
});
135178
});

tIED/updateIED.testfile.ts

Lines changed: 126 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export const scl = `<SCL xmlns="http://www.iec.ch/61850/2003/SCL" version="2007"
161161
</Server>
162162
</AccessPoint>
163163
</IED>
164-
<IED name="Subscriber2" desc="Subscriber1" manufacturer="Dummy">
164+
<IED name="Subscriber2" desc="Subscriber2" manufacturer="Dummy">
165165
<Services>
166166
<SupSubscription maxGo="4" maxSv="4"/>
167167
</Services>
@@ -215,7 +215,132 @@ export const scl = `<SCL xmlns="http://www.iec.ch/61850/2003/SCL" version="2007"
215215
</DOI>
216216
</LN>
217217
</LDevice>
218+
<LDevice inst="TestObjRef">
219+
<LN lnType="MyCSWI" prefix="CB1" lnClass="CSWI" inst="1">
220+
<DOI name="Pos">
221+
<SDI name="Oper">
222+
<DAI name="ctlVal"/>
223+
<SDI name="origin">
224+
<DAI name="orCat"/>
225+
</SDI>
226+
</SDI>
227+
<DAI name="stVal"/>
228+
<DAI name="ctlModel">
229+
<Val>sbo-with-enhanced-security</Val>
230+
</DAI>
231+
<DAI name="sboTimeout">
232+
<Val>10000</Val>
233+
</DAI>
234+
<DAI name="stSeld">
235+
<Val>false</Val>
236+
</DAI>
237+
</DOI>
238+
<DOI name="InRef1">
239+
<DAI name="setSrcRef">
240+
<Val>Subscriber2TestObjRef/CB1CILO1.EnaOpn.stVal</Val>
241+
</DAI>
242+
</DOI>
243+
<DOI name="InRef2">
244+
<DAI name="setSrcRef">
245+
<Val>Subscriber2TestObjRef/CB2CILO1.EnaCls.stVal</Val>
246+
</DAI>
247+
</DOI>
248+
<DOI name="InRef3">
249+
<DAI name="setSrcRef">
250+
<Val>Subscriber2TestObjRef/CB3CILO1.EnaOpn.stVal</Val>
251+
</DAI>
252+
</DOI>
253+
<DOI name="InRef4">
254+
<DAI name="setSrcRef" valKind="Conf" valImport="true">
255+
<Val>Subscriber2TestObjRef/CB4CILO1.EnaCls.stVal</Val>
256+
</DAI>
257+
</DOI>
258+
<DOI name="InRef5">
259+
<DAI name="setSrcRef" valKind="Conf" valImport="true">
260+
<Val>Subscriber2TestObjRef/CB5CILO1.EnaCls.stVal</Val>
261+
</DAI>
262+
</DOI>
263+
<DOI name="InRef6">
264+
<DAI name="setSrcRef" valKind="Conf" valImport="true">
265+
<Val>SomeOtherIEDObjRef/CB5CILO1.EnaCls.stVal</Val>
266+
</DAI>
267+
</DOI>
268+
<DOI name="InRef7">
269+
<DAI name="setSrcRef" valKind="Conf" valImport="true">
270+
</DAI>
271+
</DOI>
272+
<DOI name="InRef8">
273+
<DAI name="setSrcRef" valKind="Conf" valImport="true">
274+
<Val>Subscriber2TestObjRef/CB5CILO1.EnaCls.stVal</Val>
275+
</DAI>
276+
</DOI>
277+
<DOI name="InRef9">
278+
<DAI name="setSrcRef" valKind="Conf" valImport="true">
279+
<Val>Subscriber2NotExistingObjRef/CB5CILO1.EnaCls.stVal</Val>
280+
</DAI>
281+
</DOI>
282+
</LN>
283+
</LDevice>
218284
</Server>
219285
</AccessPoint>
220286
</IED>
287+
<DataTypeTemplates>
288+
<LNodeType id="MyCSWI" lnClass="CSWI">
289+
<DO name="Beh" type="behENS"/>
290+
<DO name="Loc" type="SPS_0"/>
291+
<DO name="LocSta" type="SPC_0"/>
292+
<DO name="OpOpn" type="ACT_0"/>
293+
<DO name="OpCls" type="ACT_0"/>
294+
<DO name="Pos" type="MyPos"/>
295+
<DO name="InRef1" type="InRefDOType1"/>
296+
<DO name="InRef2" type="InRefDOType2"/>
297+
<DO name="InRef3" type="InRefDOType3"/>
298+
<DO name="InRef4" type="InRefDOType45"/>
299+
<DO name="InRef5" type="InRefDOType45"/>
300+
<DO name="InRef6" type="InRefDOType3"/>
301+
<DO name="InRef7" type="InRefDOType45"/>
302+
<DO name="InRef8" type="InRefDOType6"/>
303+
<DO name="InRef9" type="InRefDOType3"/>
304+
</LNodeType>
305+
<DOType id="InRefDOType1" cdc="ORG">
306+
<DA name="setSrcRef" bType="ObjRef" valKind="RO" dchg="true" fc="SP"/>
307+
</DOType>
308+
<DOType id="InRefDOType2" cdc="ORG">
309+
<DA name="setSrcRef" bType="ObjRef" valKind="RO" valImport="true" dchg="true" fc="SP"/>
310+
</DOType>
311+
<DOType id="InRefDOType3" cdc="ORG">
312+
<DA name="setSrcRef" bType="ObjRef" valKind="Conf" valImport="true" dchg="true" fc="SP"/>
313+
</DOType>
314+
<DOType id="InRefDOType45" cdc="ORG">
315+
<DA name="setSrcRef" bType="ObjRef" dchg="true" fc="SP"/>
316+
</DOType>
317+
<DOType id="InRefDOType6" cdc="ORG">
318+
<!--Whoops, sorry - no DA! -->
319+
<DA name="setSrcRefX" bType="ObjRef" dchg="true" fc="SP"/>
320+
</DOType>
321+
<DOType id="MyPos" cdc="DPC">
322+
<DA name="SBOw" bType="Struct" type="SomeMissingType1" fc="CO"/>
323+
<DA name="Oper" bType="Struct" type="SomeMissingType2" fc="CO"/>
324+
<DA name="Cancel" bType="Struct" type="SomeMissingType3" fc="CO"/>
325+
<DA name="stVal" bType="Dbpos" dchg="true" fc="ST"/>
326+
<DA name="q" sAddr="1002" bType="Quality" qchg="true" fc="ST"/>
327+
<DA name="t" bType="Timestamp" fc="ST"/>
328+
<DA name="stSeld" bType="BOOLEAN" dchg="true" fc="ST">
329+
<Val>false</Val>
330+
</DA>
331+
<DA name="ctlModel" bType="Enum" valKind="RO" type="CtlModelKindEnum" dchg="true" fc="CF"/>
332+
<DA name="sboTimeout" bType="INT32U" valKind="RO" dchg="true" fc="CF">
333+
<Val>10000</Val>
334+
</DA>
335+
<DA name="operTimeout" bType="INT32U" valKind="RO" dchg="true" fc="CF">
336+
<Val>1000</Val>
337+
</DA>
338+
</DOType>
339+
<EnumType id="CtlModelKindEnum">
340+
<EnumVal ord="0">status-only</EnumVal>
341+
<EnumVal ord="1">direct-with-normal-security</EnumVal>
342+
<EnumVal ord="3">direct-with-enhanced-security</EnumVal>
343+
<EnumVal ord="4">sbo-with-enhanced-security</EnumVal>
344+
</EnumType>
345+
</DataTypeTemplates>
221346
</SCL>`;

tIED/updateIED.ts

Lines changed: 118 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Edit, Update } from "../foundation/utils.js";
1+
import { createElement, Edit, Update } from "../foundation/utils.js";
22

33
import { controlBlockObjRef } from "../tControl/controlBlockObjRef.js";
44

@@ -30,7 +30,11 @@ function updateIEDNameTextContent(
3030

3131
return [
3232
{ node },
33-
{ parent: iedName, node: document.createTextNode(newIedName), reference: null },
33+
{
34+
parent: iedName,
35+
node: document.createTextNode(newIedName),
36+
reference: null,
37+
},
3438
];
3539
});
3640
}
@@ -134,6 +138,114 @@ function updateIedNameAttributes(
134138
});
135139
}
136140

141+
function objectReferenceToIed(dai: Element, oldIedName: string): boolean {
142+
const val = dai.querySelector(":scope > Val")!;
143+
const valContent = val.textContent;
144+
145+
if (!valContent || !valContent?.startsWith(oldIedName)) return false;
146+
147+
const lDeviceName = valContent.slice(oldIedName.length).split("/")[0];
148+
const ied = dai.closest("IED");
149+
150+
const hasLDevice = ied?.querySelector(
151+
`:scope > AccessPoint > Server > LDevice[inst="${lDeviceName}"]`,
152+
);
153+
if (!hasLDevice) return false;
154+
155+
return true;
156+
}
157+
158+
function canModifyDA(daiOrdaType: Element): boolean {
159+
const valImport = daiOrdaType.getAttribute("valImport");
160+
const valKind = daiOrdaType.getAttribute("valKind");
161+
return (
162+
valImport === "true" && valKind !== null && ["Conf", "RO"].includes(valKind)
163+
);
164+
}
165+
166+
function objRefDetails(
167+
anyLn: Element,
168+
doName: string,
169+
daName: string,
170+
): { bType?: string | null; canModify?: boolean } | undefined {
171+
const doc = anyLn.ownerDocument;
172+
const lNodeType = doc.querySelector(
173+
`:root > DataTypeTemplates > LNodeType[id="${anyLn.getAttribute(
174+
"lnType",
175+
)}"]`,
176+
);
177+
178+
let leaf: Element | null | undefined = lNodeType;
179+
180+
const dO: Element | null | undefined = leaf?.querySelector(
181+
`DO[name="${doName}"], SDO[name="${doName}"]`,
182+
);
183+
leaf = doc.querySelector(
184+
`:root > DataTypeTemplates > DOType[id="${dO?.getAttribute("type")}"]`,
185+
);
186+
187+
const dA: Element | null | undefined = leaf?.querySelector(
188+
`DA[name="${daName}"]`,
189+
);
190+
if (!dA) return undefined;
191+
192+
const bType = dA.getAttribute("bType");
193+
const canModify = canModifyDA(dA);
194+
195+
return { bType, canModify };
196+
}
197+
198+
/** Find references used by the IED with the basic type of object reference.
199+
* Then check if they require replacement.
200+
* This function does not process LGOS and LSVS GoCBRef as these are
201+
* handled separately.
202+
*/
203+
function updateObjectReferences(
204+
ied: Element,
205+
oldIedName: string,
206+
newIedName: string,
207+
checkPermission = false,
208+
): Edit[] {
209+
const objRefCandidates = Array.from(
210+
ied.querySelectorAll("LN DAI > Val, LN0 DAI > Val"),
211+
).filter((val) => {
212+
const dai = val.parentElement!;
213+
const ln = dai.closest("LN, LN0");
214+
const lnClass = ln?.getAttribute("lnClass");
215+
const doiName = dai.closest("DOI, SDI")?.getAttribute("name");
216+
const daiName = dai.getAttribute("name");
217+
const isSupervision =
218+
lnClass &&
219+
doiName &&
220+
["LGOS", "LSVS"].includes(lnClass) &&
221+
["GoCBRef", "SvCBRef"].includes(doiName);
222+
if (!ln || !doiName || !daiName || isSupervision) return false;
223+
224+
const objRefInfo = objRefDetails(ln, doiName, daiName);
225+
226+
return (
227+
objRefInfo?.bType === "ObjRef" &&
228+
(checkPermission === false ||
229+
objRefInfo?.canModify ||
230+
canModifyDA(dai)) &&
231+
objectReferenceToIed(dai, oldIedName)
232+
);
233+
});
234+
235+
return objRefCandidates.flatMap((val) => {
236+
const objRef = val.textContent!;
237+
const textContent = `${newIedName}${objRef.slice(oldIedName.length)}`;
238+
239+
const newVal = createElement(val.ownerDocument, "Val", {});
240+
newVal.textContent = textContent;
241+
242+
return [
243+
{ node: val },
244+
{ parent: val.parentElement!, node: newVal, reference: null },
245+
];
246+
});
247+
}
248+
137249
/**
138250
* Function to schema valid update name and other attribute(s) in IED element
139251
* (rename IED)
@@ -145,9 +257,11 @@ function updateIedNameAttributes(
145257
* 3. Updates IEDName elements text content
146258
* ```
147259
* @param update - IED element and attributes to be changed in the IED element
260+
* @param checkPermission - Check permission before changing object references
261+
* (other than supervision node GoCBRef values).
148262
* @returns - Set of addition edits updating all references SCL elements
149263
*/
150-
export function updateIED(update: Update): Edit[] {
264+
export function updateIED(update: Update, checkPermission = false): Edit[] {
151265
if (update.element.tagName !== "IED") return [];
152266
if (!update.attributes.name) return [update];
153267

@@ -161,5 +275,6 @@ export function updateIED(update: Update): Edit[] {
161275
...updateIedNameAttributes(ied, oldIedName, newIedName),
162276
...updateSubscriptionSupervision(ied, oldIedName, newIedName),
163277
...updateIEDNameTextContent(ied, oldIedName, newIedName),
278+
...updateObjectReferences(ied, oldIedName, newIedName, checkPermission),
164279
];
165280
}

0 commit comments

Comments
 (0)