Skip to content

Commit 2066e4c

Browse files
authored
fix: supervision updates after ied rename (openscd#1338)
* fix: supervision updates after ied rename Signed-off-by: Stef3st <[email protected]> * chore: improve efficiency and tidy up Signed-off-by: Stef3st <[email protected]> --------- Signed-off-by: Stef3st <[email protected]>
1 parent 1c2ef60 commit 2066e4c

File tree

3 files changed

+315
-76
lines changed

3 files changed

+315
-76
lines changed

packages/open-scd/src/wizards/foundation/references.ts

Lines changed: 181 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@ import {
22
Delete,
33
getNameAttribute,
44
isPublic,
5-
Replace
6-
} from "../../foundation.js";
5+
Replace,
6+
} from '../../foundation.js';
77

88
const referenceInfoTags = ['IED', 'Substation', 'VoltageLevel', 'Bay'] as const;
99
type ReferencesInfoTag = typeof referenceInfoTags[number];
1010

11-
type FilterFunction = (element: Element, attributeName: string | null, oldName: string | null) => string;
11+
type FilterFunction = (
12+
element: Element,
13+
attributeName: string | null,
14+
oldName: string | null
15+
) => string;
1216

1317
/*
1418
* For every supported tag a list of information about which elements to search for and which attribute value
@@ -23,60 +27,82 @@ const referenceInfos: Record<
2327
filter: FilterFunction;
2428
}[]
2529
> = {
26-
IED:
27-
[{
30+
IED: [
31+
{
2832
attributeName: 'iedName',
29-
filter: simpleAttributeFilter(`Association`)
30-
}, {
33+
filter: simpleAttributeFilter(`Association`),
34+
},
35+
{
3136
attributeName: 'iedName',
32-
filter: simpleAttributeFilter(`ClientLN`)
33-
}, {
37+
filter: simpleAttributeFilter(`ClientLN`),
38+
},
39+
{
3440
attributeName: 'iedName',
35-
filter: simpleAttributeFilter(`ConnectedAP`)
36-
}, {
41+
filter: simpleAttributeFilter(`ConnectedAP`),
42+
},
43+
{
3744
attributeName: 'iedName',
38-
filter: simpleAttributeFilter(`ExtRef`)
39-
}, {
45+
filter: simpleAttributeFilter(`ExtRef`),
46+
},
47+
{
4048
attributeName: 'iedName',
41-
filter: simpleAttributeFilter(`KDC`)
42-
}, {
49+
filter: simpleAttributeFilter(`KDC`),
50+
},
51+
{
4352
attributeName: 'iedName',
44-
filter: simpleAttributeFilter(`LNode`)
45-
}, {
53+
filter: simpleAttributeFilter(`LNode`),
54+
},
55+
{
4656
attributeName: null,
47-
filter: simpleTextContentFilter(`GSEControl > IEDName`)
48-
}, {
57+
filter: simpleTextContentFilter(`GSEControl > IEDName`),
58+
},
59+
{
4960
attributeName: null,
50-
filter: simpleTextContentFilter(`SampledValueControl > IEDName`)
51-
}],
52-
Substation:
53-
[{
61+
filter: simpleTextContentFilter(`SampledValueControl > IEDName`),
62+
},
63+
{
64+
attributeName: null,
65+
filter: simpleTextContentFilter(`LN > DOI > DAI > Val`),
66+
},
67+
],
68+
Substation: [
69+
{
5470
attributeName: 'substationName',
55-
filter: simpleAttributeFilter(`Terminal`)
56-
}],
57-
VoltageLevel:
58-
[{
71+
filter: simpleAttributeFilter(`Terminal`),
72+
},
73+
],
74+
VoltageLevel: [
75+
{
5976
attributeName: 'voltageLevelName',
60-
filter: attributeFilterWithParentNameAttribute(`Terminal`,
61-
{'Substation': 'substationName'})
62-
}],
63-
Bay:
64-
[{
77+
filter: attributeFilterWithParentNameAttribute(`Terminal`, {
78+
Substation: 'substationName',
79+
}),
80+
},
81+
],
82+
Bay: [
83+
{
6584
attributeName: 'bayName',
66-
filter: attributeFilterWithParentNameAttribute(`Terminal`,
67-
{'Substation': 'substationName', 'VoltageLevel': 'voltageLevelName'})
68-
}],
69-
}
85+
filter: attributeFilterWithParentNameAttribute(`Terminal`, {
86+
Substation: 'substationName',
87+
VoltageLevel: 'voltageLevelName',
88+
}),
89+
},
90+
],
91+
};
7092

7193
/**
7294
* Simple function to create a filter to find Elements where the value of an attribute equals the old name.
7395
*
7496
* @param tagName - The tagName of the elements to search for.
7597
*/
7698
function simpleAttributeFilter(tagName: string) {
77-
return function filter(element: Element, attributeName: string | null, oldName: string | null): string {
99+
return function filter(
100+
element: Element,
101+
attributeName: string | null,
102+
oldName: string | null
103+
): string {
78104
return `${tagName}[${attributeName}="${oldName}"]`;
79-
}
105+
};
80106
}
81107

82108
/**
@@ -88,7 +114,7 @@ function simpleAttributeFilter(tagName: string) {
88114
function simpleTextContentFilter(elementQuery: string) {
89115
return function filter(): string {
90116
return `${elementQuery}`;
91-
}
117+
};
92118
}
93119

94120
/**
@@ -105,19 +131,28 @@ function simpleTextContentFilter(elementQuery: string) {
105131
* @param parentInfo - The records of parent to search for, the key is the tagName of the parent, the value
106132
* is the name of the attribuet to use in the query.
107133
*/
108-
function attributeFilterWithParentNameAttribute(tagName: string, parentInfo: Record<string, string>) {
109-
return function filter(element: Element, attributeName: string | null, oldName: string | null): string {
110-
return `${tagName}${Object.entries(parentInfo)
111-
.map(([parentTag, parentAttribute]) => {
112-
const parentElement = element.closest(parentTag);
113-
if (parentElement && parentElement.hasAttribute('name')) {
114-
const name = parentElement.getAttribute('name');
115-
return `[${parentAttribute}="${name}"]`;
116-
}
117-
return null;
118-
}).join('') // Join the strings to 1 string without a separator.
134+
function attributeFilterWithParentNameAttribute(
135+
tagName: string,
136+
parentInfo: Record<string, string>
137+
) {
138+
return function filter(
139+
element: Element,
140+
attributeName: string | null,
141+
oldName: string | null
142+
): string {
143+
return `${tagName}${
144+
Object.entries(parentInfo)
145+
.map(([parentTag, parentAttribute]) => {
146+
const parentElement = element.closest(parentTag);
147+
if (parentElement && parentElement.hasAttribute('name')) {
148+
const name = parentElement.getAttribute('name');
149+
return `[${parentAttribute}="${name}"]`;
150+
}
151+
return null;
152+
})
153+
.join('') // Join the strings to 1 string without a separator.
119154
}[${attributeName}="${oldName}"]`;
120-
}
155+
};
121156
}
122157

123158
/**
@@ -129,7 +164,11 @@ function attributeFilterWithParentNameAttribute(tagName: string, parentInfo: Rec
129164
* @param value - The value to set on the cloned element or if null remove the attribute.
130165
* @returns Returns the cloned element.
131166
*/
132-
function cloneElement(element: Element, attributeName: string, value: string): Element {
167+
function cloneElement(
168+
element: Element,
169+
attributeName: string,
170+
value: string
171+
): Element {
133172
const newElement = <Element>element.cloneNode(false);
134173
newElement.setAttribute(attributeName, value);
135174
return newElement;
@@ -142,7 +181,10 @@ function cloneElement(element: Element, attributeName: string, value: string): E
142181
* @param value - The value to set.
143182
* @returns Returns the cloned element.
144183
*/
145-
function cloneElementAndTextContent(element: Element, value: string | null): Element {
184+
function cloneElementAndTextContent(
185+
element: Element,
186+
value: string | null
187+
): Element {
146188
const newElement = <Element>element.cloneNode(false);
147189
newElement.textContent = value;
148190
return newElement;
@@ -160,7 +202,11 @@ function cloneElementAndTextContent(element: Element, value: string | null): Ele
160202
* @param newName - The new name of the element.
161203
* @returns Returns a list of Replace Actions that can be added to a Complex Action or returned directly for execution.
162204
*/
163-
export function updateReferences(element: Element, oldName: string | null, newName: string): Replace[] {
205+
export function updateReferences(
206+
element: Element,
207+
oldName: string | null,
208+
newName: string
209+
): Replace[] {
164210
if (oldName === null || oldName === newName) {
165211
return [];
166212
}
@@ -179,9 +225,13 @@ export function updateReferences(element: Element, oldName: string | null, newNa
179225
Array.from(element.ownerDocument.querySelectorAll(`${filter}`))
180226
.filter(isPublic)
181227
.forEach(element => {
182-
const newElement = cloneElement(element, info.attributeName!, newName);
183-
actions.push({old: {element}, new: {element: newElement}});
184-
})
228+
const newElement = cloneElement(
229+
element,
230+
info.attributeName!,
231+
newName
232+
);
233+
actions.push({ old: { element }, new: { element: newElement } });
234+
});
185235
} else {
186236
// If the text content needs to be updated, filter on the text content can't be done in a CSS Selector.
187237
// So we query all elements the may need to be updated and filter them afterwards.
@@ -190,10 +240,71 @@ export function updateReferences(element: Element, oldName: string | null, newNa
190240
.filter(isPublic)
191241
.forEach(element => {
192242
const newElement = cloneElementAndTextContent(element, newName);
193-
actions.push({old: {element}, new: {element: newElement}});
194-
})
243+
actions.push({ old: { element }, new: { element: newElement } });
244+
});
195245
}
196-
})
246+
});
247+
248+
if (element.tagName === 'IED')
249+
actions.push(...updateVals(element, oldName, newName));
250+
return actions;
251+
}
252+
253+
/**
254+
* Adds Replace actions to update supervision references.
255+
* Only a maximum of one Val element per IED with ExtRef elements that contain src attributes will be altered.
256+
* The Val element that needs to be altered will be found by checking if the controlBlockReference complies with this element.
257+
* The controlBlockReference needs to contain the IED that gets renamed.
258+
*
259+
* @param element - The element for which the name is updated.
260+
* @param oldName - The old name of the element.
261+
* @param newName - The new name of the element.
262+
*/
263+
function updateVals(element: Element, oldName: string | null, newName: string) {
264+
const actions: Replace[] = [];
265+
const ieds = element.ownerDocument.querySelectorAll('IED');
266+
ieds.forEach(ied => {
267+
// All Val elements inside LGOS and LSVS lnClasses that starts with the IED name that needs to be changed will be gathered.
268+
// Because of a very rare case where multiple IED start with the same name, all will be gathered.
269+
// If none are found continue to the next IED.
270+
const valValues: Element[] = Array.from(
271+
ied.querySelectorAll(
272+
`:scope > AccessPoint > Server > LDevice > LN[lnClass="LGOS"] > DOI > DAI > Val, :scope > AccessPoint > Server > LDevice > LN[lnClass="LSVS"] > DOI > DAI > Val`
273+
)
274+
);
275+
276+
if (valValues.length === 0) return;
277+
278+
// If atleast one extRef element contains the to-be-changed IED name and has a srcCBName, one will be gathered.
279+
// From that extRef element a controlblockreferences will be created and compared to the Val elements.
280+
// If a match is found, the name of that Val element will be changed accordingly and the loop will be broken, as only 1 Val element need to be changed.
281+
282+
const ref = ied.querySelector(
283+
`:scope > AccessPoint > Server > LDevice > LN0 > Inputs > ExtRef[iedName="${oldName}"][srcCBName]`
284+
);
285+
286+
const suffixCBReference =
287+
ref?.getAttribute('srcLDInst') +
288+
'/' +
289+
ref?.getAttribute('srcLNClass') +
290+
'.' +
291+
ref?.getAttribute('srcCBName');
292+
293+
for (let value of valValues) {
294+
if (oldName + suffixCBReference === value.textContent!.trim()) {
295+
const newElement = cloneElementAndTextContent(
296+
value,
297+
newName + suffixCBReference
298+
);
299+
actions.push({
300+
old: { element: value },
301+
new: { element: newElement },
302+
});
303+
break;
304+
}
305+
}
306+
});
307+
197308
return actions;
198309
}
199310

@@ -225,8 +336,8 @@ export function deleteReferences(element: Element): Delete[] {
225336
Array.from(element.ownerDocument.querySelectorAll(`${filter}`))
226337
.filter(isPublic)
227338
.forEach(element => {
228-
actions.push({old: { parent: element.parentElement!, element }});
229-
})
339+
actions.push({ old: { parent: element.parentElement!, element } });
340+
});
230341
} else {
231342
// If the text content needs to be used for filtering, filter on the text content can't be done in a CSS Selector.
232343
// So we query all elements the may need to be deleted and filter them afterwards.
@@ -236,10 +347,15 @@ export function deleteReferences(element: Element): Delete[] {
236347
.forEach(element => {
237348
// We not only need to remove the element containing the text content, but the parent of this element.
238349
if (element.parentElement) {
239-
actions.push({old: {parent: element.parentElement.parentElement!, element: element.parentElement}});
350+
actions.push({
351+
old: {
352+
parent: element.parentElement.parentElement!,
353+
element: element.parentElement,
354+
},
355+
});
240356
}
241-
})
357+
});
242358
}
243-
})
359+
});
244360
return actions;
245361
}

0 commit comments

Comments
 (0)