@@ -2,13 +2,17 @@ import {
2
2
Delete ,
3
3
getNameAttribute ,
4
4
isPublic ,
5
- Replace
6
- } from " ../../foundation.js" ;
5
+ Replace ,
6
+ } from ' ../../foundation.js' ;
7
7
8
8
const referenceInfoTags = [ 'IED' , 'Substation' , 'VoltageLevel' , 'Bay' ] as const ;
9
9
type ReferencesInfoTag = typeof referenceInfoTags [ number ] ;
10
10
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 ;
12
16
13
17
/*
14
18
* 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<
23
27
filter : FilterFunction ;
24
28
} [ ]
25
29
> = {
26
- IED :
27
- [ {
30
+ IED : [
31
+ {
28
32
attributeName : 'iedName' ,
29
- filter : simpleAttributeFilter ( `Association` )
30
- } , {
33
+ filter : simpleAttributeFilter ( `Association` ) ,
34
+ } ,
35
+ {
31
36
attributeName : 'iedName' ,
32
- filter : simpleAttributeFilter ( `ClientLN` )
33
- } , {
37
+ filter : simpleAttributeFilter ( `ClientLN` ) ,
38
+ } ,
39
+ {
34
40
attributeName : 'iedName' ,
35
- filter : simpleAttributeFilter ( `ConnectedAP` )
36
- } , {
41
+ filter : simpleAttributeFilter ( `ConnectedAP` ) ,
42
+ } ,
43
+ {
37
44
attributeName : 'iedName' ,
38
- filter : simpleAttributeFilter ( `ExtRef` )
39
- } , {
45
+ filter : simpleAttributeFilter ( `ExtRef` ) ,
46
+ } ,
47
+ {
40
48
attributeName : 'iedName' ,
41
- filter : simpleAttributeFilter ( `KDC` )
42
- } , {
49
+ filter : simpleAttributeFilter ( `KDC` ) ,
50
+ } ,
51
+ {
43
52
attributeName : 'iedName' ,
44
- filter : simpleAttributeFilter ( `LNode` )
45
- } , {
53
+ filter : simpleAttributeFilter ( `LNode` ) ,
54
+ } ,
55
+ {
46
56
attributeName : null ,
47
- filter : simpleTextContentFilter ( `GSEControl > IEDName` )
48
- } , {
57
+ filter : simpleTextContentFilter ( `GSEControl > IEDName` ) ,
58
+ } ,
59
+ {
49
60
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
+ {
54
70
attributeName : 'substationName' ,
55
- filter : simpleAttributeFilter ( `Terminal` )
56
- } ] ,
57
- VoltageLevel :
58
- [ {
71
+ filter : simpleAttributeFilter ( `Terminal` ) ,
72
+ } ,
73
+ ] ,
74
+ VoltageLevel : [
75
+ {
59
76
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
+ {
65
84
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
+ } ;
70
92
71
93
/**
72
94
* Simple function to create a filter to find Elements where the value of an attribute equals the old name.
73
95
*
74
96
* @param tagName - The tagName of the elements to search for.
75
97
*/
76
98
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 {
78
104
return `${ tagName } [${ attributeName } ="${ oldName } "]` ;
79
- }
105
+ } ;
80
106
}
81
107
82
108
/**
@@ -88,7 +114,7 @@ function simpleAttributeFilter(tagName: string) {
88
114
function simpleTextContentFilter ( elementQuery : string ) {
89
115
return function filter ( ) : string {
90
116
return `${ elementQuery } ` ;
91
- }
117
+ } ;
92
118
}
93
119
94
120
/**
@@ -105,19 +131,28 @@ function simpleTextContentFilter(elementQuery: string) {
105
131
* @param parentInfo - The records of parent to search for, the key is the tagName of the parent, the value
106
132
* is the name of the attribuet to use in the query.
107
133
*/
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.
119
154
} [${ attributeName } ="${ oldName } "]`;
120
- }
155
+ } ;
121
156
}
122
157
123
158
/**
@@ -129,7 +164,11 @@ function attributeFilterWithParentNameAttribute(tagName: string, parentInfo: Rec
129
164
* @param value - The value to set on the cloned element or if null remove the attribute.
130
165
* @returns Returns the cloned element.
131
166
*/
132
- function cloneElement ( element : Element , attributeName : string , value : string ) : Element {
167
+ function cloneElement (
168
+ element : Element ,
169
+ attributeName : string ,
170
+ value : string
171
+ ) : Element {
133
172
const newElement = < Element > element . cloneNode ( false ) ;
134
173
newElement . setAttribute ( attributeName , value ) ;
135
174
return newElement ;
@@ -142,7 +181,10 @@ function cloneElement(element: Element, attributeName: string, value: string): E
142
181
* @param value - The value to set.
143
182
* @returns Returns the cloned element.
144
183
*/
145
- function cloneElementAndTextContent ( element : Element , value : string | null ) : Element {
184
+ function cloneElementAndTextContent (
185
+ element : Element ,
186
+ value : string | null
187
+ ) : Element {
146
188
const newElement = < Element > element . cloneNode ( false ) ;
147
189
newElement . textContent = value ;
148
190
return newElement ;
@@ -160,7 +202,11 @@ function cloneElementAndTextContent(element: Element, value: string | null): Ele
160
202
* @param newName - The new name of the element.
161
203
* @returns Returns a list of Replace Actions that can be added to a Complex Action or returned directly for execution.
162
204
*/
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 [ ] {
164
210
if ( oldName === null || oldName === newName ) {
165
211
return [ ] ;
166
212
}
@@ -179,9 +225,13 @@ export function updateReferences(element: Element, oldName: string | null, newNa
179
225
Array . from ( element . ownerDocument . querySelectorAll ( `${ filter } ` ) )
180
226
. filter ( isPublic )
181
227
. 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
+ } ) ;
185
235
} else {
186
236
// If the text content needs to be updated, filter on the text content can't be done in a CSS Selector.
187
237
// 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
190
240
. filter ( isPublic )
191
241
. forEach ( element => {
192
242
const newElement = cloneElementAndTextContent ( element , newName ) ;
193
- actions . push ( { old : { element} , new : { element : newElement } } ) ;
194
- } )
243
+ actions . push ( { old : { element } , new : { element : newElement } } ) ;
244
+ } ) ;
195
245
}
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
+
197
308
return actions ;
198
309
}
199
310
@@ -225,8 +336,8 @@ export function deleteReferences(element: Element): Delete[] {
225
336
Array . from ( element . ownerDocument . querySelectorAll ( `${ filter } ` ) )
226
337
. filter ( isPublic )
227
338
. forEach ( element => {
228
- actions . push ( { old : { parent : element . parentElement ! , element } } ) ;
229
- } )
339
+ actions . push ( { old : { parent : element . parentElement ! , element } } ) ;
340
+ } ) ;
230
341
} else {
231
342
// If the text content needs to be used for filtering, filter on the text content can't be done in a CSS Selector.
232
343
// 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[] {
236
347
. forEach ( element => {
237
348
// We not only need to remove the element containing the text content, but the parent of this element.
238
349
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
+ } ) ;
240
356
}
241
- } )
357
+ } ) ;
242
358
}
243
- } )
359
+ } ) ;
244
360
return actions ;
245
361
}
0 commit comments