1
- import { isPublic , SimpleAction } from "../../foundation.js" ;
1
+ import {
2
+ isPublic ,
3
+ Replace
4
+ } from "../../foundation.js" ;
2
5
3
- const referenceInfoTags = [ 'IED' , 'Substation' ] as const ;
6
+ const referenceInfoTags = [ 'IED' , 'Substation' , 'VoltageLevel' , 'Bay' ] as const ;
4
7
type ReferencesInfoTag = typeof referenceInfoTags [ number ] ;
5
8
9
+ type FilterFunction = ( element : Element , attributeName : string | null , oldName : string | null ) => string ;
10
+
6
11
/*
7
12
* For every supported tag a list of information about which elements to search for and which attribute value
8
13
* to replace with the new value typed in the screen by the user. This is used to update references to a name
9
14
* of an element by other elements.
10
- * If the attribute is null the text content of the found element will be replaced.
15
+ * If the attributeName is null the text content of the found element will be replaced.
11
16
*/
12
17
const referenceInfos : Record <
13
18
ReferencesInfoTag ,
14
19
{
15
- elementQuery : string ;
16
- attribute : string | null ;
20
+ attributeName : string | null ;
21
+ filter : FilterFunction ;
17
22
} [ ]
18
23
> = {
19
24
IED :
20
25
[ {
21
- elementQuery : `Association` ,
22
- attribute : 'iedName'
26
+ attributeName : 'iedName' ,
27
+ filter : simpleAttributeFilter ( `Association` )
23
28
} , {
24
- elementQuery : `ClientLN` ,
25
- attribute : 'iedName'
29
+ attributeName : 'iedName' ,
30
+ filter : simpleAttributeFilter ( `ClientLN` )
26
31
} , {
27
- elementQuery : `ConnectedAP` ,
28
- attribute : 'iedName'
32
+ attributeName : 'iedName' ,
33
+ filter : simpleAttributeFilter ( `ConnectedAP` )
29
34
} , {
30
- elementQuery : `ExtRef` ,
31
- attribute : 'iedName'
35
+ attributeName : 'iedName' ,
36
+ filter : simpleAttributeFilter ( `ExtRef` )
32
37
} , {
33
- elementQuery : `KDC` ,
34
- attribute : 'iedName'
38
+ attributeName : 'iedName' ,
39
+ filter : simpleAttributeFilter ( `KDC` )
35
40
} , {
36
- elementQuery : `LNode` ,
37
- attribute : 'iedName'
41
+ attributeName : 'iedName' ,
42
+ filter : simpleAttributeFilter ( `LNode` )
38
43
} , {
39
- elementQuery : `GSEControl > IEDName` ,
40
- attribute : null
44
+ attributeName : null ,
45
+ filter : simpleTextContentFilter ( `GSEControl > IEDName` )
41
46
} , {
42
- elementQuery : `SampledValueControl > IEDName` ,
43
- attribute : null
47
+ attributeName : null ,
48
+ filter : simpleTextContentFilter ( `SampledValueControl > IEDName` )
44
49
} ] ,
45
50
Substation :
46
51
[ {
47
- elementQuery : `Terminal` ,
48
- attribute : 'substationName'
49
- } ]
52
+ attributeName : 'substationName' ,
53
+ filter : simpleAttributeFilter ( `Terminal` )
54
+ } ] ,
55
+ VoltageLevel :
56
+ [ {
57
+ attributeName : 'voltageLevelName' ,
58
+ filter : attributeFilterWithParentNameAttribute ( `Terminal` ,
59
+ { 'Substation' : 'substationName' } )
60
+ } ] ,
61
+ Bay :
62
+ [ {
63
+ attributeName : 'bayName' ,
64
+ filter : attributeFilterWithParentNameAttribute ( `Terminal` ,
65
+ { 'Substation' : 'substationName' , 'VoltageLevel' : 'voltageLevelName' } )
66
+ } ] ,
50
67
}
51
68
69
+ /**
70
+ * Simple function to create a filter to find Elements where the value of an attribute equals the old name.
71
+ *
72
+ * @param tagName - The tagName of the elements to search for.
73
+ */
74
+ function simpleAttributeFilter ( tagName : string ) {
75
+ return function filter ( element : Element , attributeName : string | null , oldName : string | null ) : string {
76
+ return `${ tagName } [${ attributeName } ="${ oldName } "]` ;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Simple function to search for Elements for which the text content may contain the old name.
82
+ * Because the text content of an element can't be search for in a CSS Selector this is done afterwards.
83
+ *
84
+ * @param elementQuery - The CSS Query to search for the Elements.
85
+ */
86
+ function simpleTextContentFilter ( elementQuery : string ) {
87
+ return function filter ( ) : string {
88
+ return `${ elementQuery } ` ;
89
+ }
90
+ }
91
+
92
+ /**
93
+ * More complex function to search for elements for which the value of an attribute needs to be updated.
94
+ * To find the correct element the name of a parent element also needs to be included in the search.
95
+ *
96
+ * For instance when the name of a Bay is updated only the terminals need to be updated where of course
97
+ * the old name of the bay is the value of the attribute 'bayName', but also the voltage level and substation
98
+ * name need to be included, because the name of the bay is only unique within the voltage level.
99
+ * The query will then become
100
+ * `Terminal[substationName="<substationName>"][voltageLevelName="<voltageLevelName>"][bayName="<oldName>"]`
101
+ *
102
+ * @param tagName - The tagName of the elements to search for.
103
+ * @param parentInfo - The records of parent to search for, the key is the tagName of the parent, the value
104
+ * is the name of the attribuet to use in the query.
105
+ */
106
+ function attributeFilterWithParentNameAttribute ( tagName : string , parentInfo : Record < string , string > ) {
107
+ return function filter ( element : Element , attributeName : string | null , oldName : string | null ) : string {
108
+ return `${ tagName } ${ Object . entries ( parentInfo )
109
+ . map ( ( [ parentTag , parentAttribute ] ) => {
110
+ const parentElement = element . closest ( parentTag ) ;
111
+ if ( parentElement && parentElement . hasAttribute ( 'name' ) ) {
112
+ const name = parentElement . getAttribute ( 'name' ) ;
113
+ return `[${ parentAttribute } ="${ name } "]` ;
114
+ }
115
+ return null ;
116
+ } ) . join ( '' ) // Join the strings to 1 string without a separator.
117
+ } [${ attributeName } ="${ oldName } "]`;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Clone an element with the attribute name passed and process the new value. If the new value
123
+ * is null the attribute will be removed otherwise the value of the attribute is updated.
124
+ *
125
+ * @param element - The element to clone.
126
+ * @param attributeName - The name of the attribute to copy.
127
+ * @param value - The value to set on the cloned element or if null remove the attribute.
128
+ * @returns Returns the cloned element.
129
+ */
52
130
function cloneElement ( element : Element , attributeName : string , value : string | null ) : Element {
53
131
const newElement = < Element > element . cloneNode ( false ) ;
54
132
if ( value === null ) {
@@ -59,14 +137,33 @@ function cloneElement(element: Element, attributeName: string, value: string | n
59
137
return newElement ;
60
138
}
61
139
140
+ /**
141
+ * Clone an element and set the value as text content on the cloned element.
142
+ *
143
+ * @param element - The element to clone.
144
+ * @param value - The value to set.
145
+ * @returns Returns the cloned element.
146
+ */
62
147
function cloneElementAndTextContent ( element : Element , value : string | null ) : Element {
63
148
const newElement = < Element > element . cloneNode ( false ) ;
64
149
newElement . textContent = value ;
65
150
return newElement ;
66
151
}
67
152
68
- export function updateReferences ( element : Element , oldValue : string | null , newValue : string ) : SimpleAction [ ] {
69
- if ( oldValue === newValue ) {
153
+ /**
154
+ * Function to create Replace actions to update reference which point to the name of the element being updated.
155
+ * For instance the IED Name is used in other SCL Elements as attribute 'iedName' to reference the IED.
156
+ * These attribute values need to be updated if the name of the IED changes.
157
+ *
158
+ * An empty array will be returned if the old and new value are the same or no references need to be updated.
159
+ *
160
+ * @param element - The element for which the name is updated.
161
+ * @param oldName - The old name of the element.
162
+ * @param newName - The new name of the element.
163
+ * @returns Returns a list of Replace Actions that can be added to a Complex Action or returned directly for execution.
164
+ */
165
+ export function updateReferences ( element : Element , oldName : string | null , newName : string ) : Replace [ ] {
166
+ if ( oldName === newName ) {
70
167
return [ ] ;
71
168
}
72
169
@@ -75,21 +172,27 @@ export function updateReferences(element: Element, oldValue: string | null, newV
75
172
return [ ] ;
76
173
}
77
174
78
- const actions : SimpleAction [ ] = [ ] ;
175
+ const actions : Replace [ ] = [ ] ;
79
176
referenceInfo . forEach ( info => {
80
- if ( info . attribute !== null ) {
81
- Array . from ( element . ownerDocument . querySelectorAll ( `${ info . elementQuery } [${ info . attribute } ="${ oldValue } "]` ) )
177
+ // Depending on if an attribute value needs to be updated or the text content of an element
178
+ // different scenarios need to be executed.
179
+ if ( info . attributeName ) {
180
+ const filter = info . filter ( element , info . attributeName , oldName ) ;
181
+ Array . from ( element . ownerDocument . querySelectorAll ( `${ filter } ` ) )
82
182
. filter ( isPublic )
83
183
. forEach ( element => {
84
- const newElement = cloneElement ( element , info . attribute ! , newValue ) ;
184
+ const newElement = cloneElement ( element , info . attributeName ! , newName ) ;
85
185
actions . push ( { old : { element} , new : { element : newElement } } ) ;
86
186
} )
87
187
} else {
88
- Array . from ( element . ownerDocument . querySelectorAll ( `${ info . elementQuery } ` ) )
89
- . filter ( element => element . textContent === oldValue )
188
+ // If the text content needs to be updated, filter on the text content can't be done in a CSS Selector.
189
+ // So we query all elements the may need to be updated and filter them afterwards.
190
+ const filter = info . filter ( element , info . attributeName , oldName ) ;
191
+ Array . from ( element . ownerDocument . querySelectorAll ( `${ filter } ` ) )
192
+ . filter ( element => element . textContent === oldName )
90
193
. filter ( isPublic )
91
194
. forEach ( element => {
92
- const newElement = cloneElementAndTextContent ( element , newValue ) ;
195
+ const newElement = cloneElementAndTextContent ( element , newName ) ;
93
196
actions . push ( { old : { element} , new : { element : newElement } } ) ;
94
197
} )
95
198
}
0 commit comments