|
| 1 | +import type { Tree } from '@openscd/oscd-tree-grid'; |
| 2 | + |
| 3 | +function getChildren(parent: Element): Element[] { |
| 4 | + if (parent.tagName === 'SCL') |
| 5 | + return Array.from(parent.querySelectorAll(':scope > Substation')); |
| 6 | + |
| 7 | + if (parent.tagName === 'Substation') |
| 8 | + return Array.from( |
| 9 | + parent.querySelectorAll( |
| 10 | + ':scope > VoltageLevel, :scope > PowerTransformer, :scope > Function' |
| 11 | + ) |
| 12 | + ); |
| 13 | + |
| 14 | + if (parent.tagName === 'PowerTransformer') |
| 15 | + return Array.from(parent.querySelectorAll(':scope > EqFunction')); |
| 16 | + |
| 17 | + if (parent.tagName === 'VoltageLevel') |
| 18 | + return Array.from( |
| 19 | + parent.querySelectorAll(':scope > Bay, :scope > Function') |
| 20 | + ); |
| 21 | + |
| 22 | + if (parent.tagName === 'Bay') |
| 23 | + return Array.from( |
| 24 | + parent.querySelectorAll(':scope > Function,:scope > ConductingEquipment') |
| 25 | + ); |
| 26 | + |
| 27 | + if (parent.tagName === 'ConductingEquipment') |
| 28 | + return Array.from( |
| 29 | + parent.querySelectorAll( |
| 30 | + ':scope > EqFunction,:scope > SubEquipment,:scope > LNode' |
| 31 | + ) |
| 32 | + ); |
| 33 | + |
| 34 | + if (parent.tagName === 'ConductingEquipment') |
| 35 | + return Array.from( |
| 36 | + parent.querySelectorAll(':scope > EqFunction,:scope > SubEquipment') |
| 37 | + ); |
| 38 | + |
| 39 | + if (parent.tagName === 'Function' || parent.tagName === 'SubFunction') |
| 40 | + return Array.from( |
| 41 | + parent.querySelectorAll(':scope > SubFunction, :scope > LNode') |
| 42 | + ); |
| 43 | + |
| 44 | + if (parent.tagName === 'EqFunction' || parent.tagName === 'EqSubFunction') |
| 45 | + return Array.from( |
| 46 | + parent.querySelectorAll(':scope > EqSubFunction, :scope > LNode') |
| 47 | + ); |
| 48 | + |
| 49 | + if (parent.tagName === 'LNode') { |
| 50 | + const lNodeType = parent.ownerDocument.querySelector( |
| 51 | + `LNodeType[id="${parent.getAttribute('lnType')}"]` |
| 52 | + ); |
| 53 | + if (!lNodeType) return []; |
| 54 | + |
| 55 | + return Array.from(lNodeType.querySelectorAll(':scope > DO')); |
| 56 | + } |
| 57 | + |
| 58 | + if (parent.tagName === 'DO' || parent.tagName === 'SDO') { |
| 59 | + const doType = parent.ownerDocument.querySelector( |
| 60 | + `DOType[id="${parent.getAttribute('type')}"]` |
| 61 | + ); |
| 62 | + if (!doType) return []; |
| 63 | + |
| 64 | + return Array.from(doType.querySelectorAll(':scope > SDO,:scope > DA')); |
| 65 | + } |
| 66 | + |
| 67 | + if (parent.tagName === 'DA' || parent.tagName === 'BDA') { |
| 68 | + const daType = parent.ownerDocument.querySelector( |
| 69 | + `DAType[id="${parent.getAttribute('type')}"]` |
| 70 | + ); |
| 71 | + if (!daType) return []; |
| 72 | + |
| 73 | + return Array.from(daType.querySelectorAll(':scope > BDA')); |
| 74 | + } |
| 75 | + |
| 76 | + return []; |
| 77 | +} |
| 78 | + |
| 79 | +function getLeafNode(element: Element): string[] { |
| 80 | + const children = getChildren(element); |
| 81 | + if (children.length === 0) return [element.getAttribute('name')!]; |
| 82 | + |
| 83 | + return children.flatMap(child => getLeafNode(child)); |
| 84 | +} |
| 85 | + |
| 86 | +function hasLeafNode(element: Element, leafNode?: string): boolean { |
| 87 | + if (!leafNode) return true; |
| 88 | + return getLeafNode(element).includes(leafNode); |
| 89 | +} |
| 90 | + |
| 91 | +function lNodeTitle(lNode: Element): string { |
| 92 | + const lNodeSpec = lNode.querySelector(':scope LNodeSpecNaming'); |
| 93 | + if (lNodeSpec) |
| 94 | + return `${lNodeSpec.getAttribute('sPrefix') ?? ''}${ |
| 95 | + lNodeSpec.getAttribute('sLnClass') ?? 'UNKNOWN_INST' |
| 96 | + }${lNodeSpec.getAttribute('sLnInst') ?? ''}`; |
| 97 | + |
| 98 | + return `${lNode.getAttribute('prefix') ?? ''}${ |
| 99 | + lNode.getAttribute('lnClass') ?? 'UNKNOWN_INST' |
| 100 | + }${lNode.getAttribute('lnInst') ?? ''}`; |
| 101 | +} |
| 102 | + |
| 103 | +function dataAttributeObject(da: Element, leafNode?: string): Tree { |
| 104 | + const tree: Tree = {}; |
| 105 | + const children: Tree = {}; |
| 106 | + |
| 107 | + getChildren(da) |
| 108 | + .filter(daChild => hasLeafNode(daChild, leafNode)) |
| 109 | + .forEach(bda => { |
| 110 | + const bdaName = bda.getAttribute('name') ?? 'UNKNOWN_BDA'; |
| 111 | + if (bda.getAttribute('bType') === 'Struct') { |
| 112 | + const id = `BDA: ${bdaName}`; |
| 113 | + children[id] = dataAttributeObject(bda, leafNode); |
| 114 | + children[id]!.text = bdaName; |
| 115 | + } else { |
| 116 | + const id = `LEAF: ${bdaName}`; |
| 117 | + children[id] = {}; |
| 118 | + children[id]!.text = bdaName; |
| 119 | + } |
| 120 | + }); |
| 121 | + |
| 122 | + tree.children = children; |
| 123 | + return tree; |
| 124 | +} |
| 125 | + |
| 126 | +function subDataObjectsObject(sdo: Element, leafNode?: string): Tree { |
| 127 | + const tree: Tree = {}; |
| 128 | + const children: Tree = {}; |
| 129 | + |
| 130 | + getChildren(sdo) |
| 131 | + .filter(sdoChild => hasLeafNode(sdoChild, leafNode)) |
| 132 | + .forEach(sDoOrDa => { |
| 133 | + if (sDoOrDa.tagName === 'SDO') { |
| 134 | + const sDoName = sDoOrDa.getAttribute('name') ?? 'UNKNOWN_SDO'; |
| 135 | + const id = `SDO: ${sDoName}`; |
| 136 | + children[id] = subDataObjectsObject(sDoOrDa, leafNode); |
| 137 | + children[id]!.text = sDoName; |
| 138 | + } else { |
| 139 | + const daName = sDoOrDa.getAttribute('name') ?? 'UNKNOWN_DA'; |
| 140 | + if (sDoOrDa.getAttribute('bType') === 'Struct') { |
| 141 | + const id = `DA: ${daName}`; |
| 142 | + children[id] = dataAttributeObject(sDoOrDa, leafNode); |
| 143 | + children[id]!.text = daName; |
| 144 | + } else { |
| 145 | + const id = `LEAF: ${daName}`; |
| 146 | + children[id] = {}; |
| 147 | + children[id]!.text = daName; |
| 148 | + } |
| 149 | + } |
| 150 | + }); |
| 151 | + |
| 152 | + tree.children = children; |
| 153 | + return tree; |
| 154 | +} |
| 155 | + |
| 156 | +function dataObjectObject(dO: Element, leafNode?: string): Tree { |
| 157 | + const tree: Tree = {}; |
| 158 | + const children: Tree = {}; |
| 159 | + |
| 160 | + getChildren(dO) |
| 161 | + .filter(dOChild => hasLeafNode(dOChild, leafNode)) |
| 162 | + .forEach(sDoOrDa => { |
| 163 | + if (sDoOrDa.tagName === 'SDO') { |
| 164 | + const sDoName = sDoOrDa.getAttribute('name') ?? 'UNKNOWN_SDO'; |
| 165 | + |
| 166 | + const id = `SDO: ${sDoName}`; |
| 167 | + children[id] = subDataObjectsObject(sDoOrDa, leafNode); |
| 168 | + children[id]!.text = sDoName; |
| 169 | + } else { |
| 170 | + const daName = sDoOrDa.getAttribute('name') ?? 'UNKNOWN_DA'; |
| 171 | + if (sDoOrDa.getAttribute('bType') === 'Struct') { |
| 172 | + const id = `DA: ${daName}`; |
| 173 | + children[id] = dataAttributeObject(sDoOrDa, leafNode); |
| 174 | + children[id]!.text = daName; |
| 175 | + } else { |
| 176 | + const id = `LEAF: ${daName}`; |
| 177 | + children[id] = {}; |
| 178 | + children[id]!.text = daName; |
| 179 | + } |
| 180 | + } |
| 181 | + }); |
| 182 | + |
| 183 | + tree.children = children; |
| 184 | + return tree; |
| 185 | +} |
| 186 | + |
| 187 | +function anyLnObject(lNode: Element, leafNode?: string): Tree { |
| 188 | + const tree: Tree = {}; |
| 189 | + const children: Tree = {}; |
| 190 | + |
| 191 | + getChildren(lNode) |
| 192 | + .filter(lNodeChild => hasLeafNode(lNodeChild, leafNode)) |
| 193 | + .forEach(dO => { |
| 194 | + const doName = dO.getAttribute('name') ?? 'UNKNOWN_DO'; |
| 195 | + |
| 196 | + const id = `DO: ${doName}`; |
| 197 | + children[id] = dataObjectObject(dO, leafNode); |
| 198 | + children[id]!.text = doName; |
| 199 | + }); |
| 200 | + |
| 201 | + tree.children = children; |
| 202 | + return tree; |
| 203 | +} |
| 204 | + |
| 205 | +function funcObject(func: Element, leafNode?: string): Tree { |
| 206 | + const tree: Tree = {}; |
| 207 | + const children: Tree = {}; |
| 208 | + |
| 209 | + getChildren(func) |
| 210 | + .filter(funcChild => hasLeafNode(funcChild, leafNode)) |
| 211 | + .forEach(funcChild => { |
| 212 | + if (funcChild.tagName === 'LNode') { |
| 213 | + const title = lNodeTitle(funcChild); |
| 214 | + |
| 215 | + const id = `${funcChild.tagName}: ${title}`; |
| 216 | + children[id] = anyLnObject(funcChild, leafNode); |
| 217 | + children[id]!.text = title; |
| 218 | + } else { |
| 219 | + const funcName = `${funcChild.getAttribute('name') ?? 'UNKNOWN_INST'}`; |
| 220 | + |
| 221 | + const id = `${funcChild.tagName}: ${funcName}`; |
| 222 | + children[id] = funcObject(funcChild, leafNode); |
| 223 | + children[id]!.text = funcName; |
| 224 | + } |
| 225 | + }); |
| 226 | + |
| 227 | + tree.children = children; |
| 228 | + |
| 229 | + return tree; |
| 230 | +} |
| 231 | + |
| 232 | +function condEqObject(condEq: Element, leafNode?: string): Tree { |
| 233 | + const tree: Tree = {}; |
| 234 | + const children: Tree = {}; |
| 235 | + |
| 236 | + getChildren(condEq) |
| 237 | + .filter(condEqChild => hasLeafNode(condEqChild, leafNode)) |
| 238 | + .forEach(condEqChild => { |
| 239 | + if (condEqChild.tagName === 'LNode') { |
| 240 | + const title = lNodeTitle(condEqChild); |
| 241 | + |
| 242 | + const id = `${condEqChild.tagName}: ${title}`; |
| 243 | + children[id] = anyLnObject(condEqChild, leafNode); |
| 244 | + children[id]!.text = title; |
| 245 | + } else if (condEqChild.tagName === 'SubEquipment') { |
| 246 | + const subEqName = `${ |
| 247 | + condEqChild.getAttribute('name') ?? 'UNKNOWN_INST' |
| 248 | + }`; |
| 249 | + |
| 250 | + const id = `${condEqChild.tagName}: ${subEqName}`; |
| 251 | + children[id] = condEqObject(condEqChild, leafNode); |
| 252 | + children[id]!.text = subEqName; |
| 253 | + } else { |
| 254 | + const funcName = `${ |
| 255 | + condEqChild.getAttribute('name') ?? 'UNKNOWN_INST' |
| 256 | + }`; |
| 257 | + |
| 258 | + const id = `${condEqChild.tagName}: ${funcName}`; |
| 259 | + children[id] = funcObject(condEqChild, leafNode); |
| 260 | + children[id]!.text = funcName; |
| 261 | + } |
| 262 | + }); |
| 263 | + |
| 264 | + tree.children = children; |
| 265 | + |
| 266 | + return tree; |
| 267 | +} |
| 268 | + |
| 269 | +function bayObject(bay: Element, leafNode?: string): Tree { |
| 270 | + const tree: Tree = {}; |
| 271 | + const children: Tree = {}; |
| 272 | + |
| 273 | + getChildren(bay) |
| 274 | + .filter(bayChild => hasLeafNode(bayChild, leafNode)) |
| 275 | + .forEach(bayChild => { |
| 276 | + if (bayChild.tagName === 'ConductingEquipment') { |
| 277 | + const condEqName = `${bayChild.getAttribute('name') ?? 'UNKNOWN_INST'}`; |
| 278 | + |
| 279 | + const id = `${bayChild.tagName}: ${condEqName}`; |
| 280 | + children[id] = condEqObject(bayChild, leafNode); |
| 281 | + children[id]!.text = condEqName; |
| 282 | + } else { |
| 283 | + const funcName = `${bayChild.getAttribute('name') ?? 'UNKNOWN_INST'}`; |
| 284 | + |
| 285 | + const id = `${bayChild.tagName}: ${funcName}`; |
| 286 | + children[id] = funcObject(bayChild, leafNode); |
| 287 | + children[id]!.text = funcName; |
| 288 | + } |
| 289 | + }); |
| 290 | + |
| 291 | + tree.children = children; |
| 292 | + |
| 293 | + return tree; |
| 294 | +} |
| 295 | + |
| 296 | +function voltLvObject(voltLv: Element, leafNode?: string): Tree { |
| 297 | + const tree: Tree = {}; |
| 298 | + const children: Tree = {}; |
| 299 | + |
| 300 | + getChildren(voltLv).forEach(voltLvChild => { |
| 301 | + if (voltLvChild.tagName === 'Bay') { |
| 302 | + const bayName = `${voltLvChild.getAttribute('name') ?? 'UNKNOWN_INST'}`; |
| 303 | + |
| 304 | + const id = `${voltLvChild.tagName}: ${bayName}`; |
| 305 | + children[id] = bayObject(voltLvChild, leafNode); |
| 306 | + children[id]!.text = bayName; |
| 307 | + } else { |
| 308 | + const funcName = `${voltLvChild.getAttribute('name') ?? 'UNKNOWN_INST'}`; |
| 309 | + |
| 310 | + const id = `${voltLvChild.tagName}: ${funcName}`; |
| 311 | + children[id] = funcObject(voltLvChild, leafNode); |
| 312 | + children[id]!.text = funcName; |
| 313 | + } |
| 314 | + }); |
| 315 | + |
| 316 | + tree.children = children; |
| 317 | + |
| 318 | + return tree; |
| 319 | +} |
| 320 | + |
| 321 | +function subStObject(subSt: Element, leafNode?: string): Tree { |
| 322 | + const tree: Tree = {}; |
| 323 | + const children: Tree = {}; |
| 324 | + |
| 325 | + getChildren(subSt).forEach(subStChild => { |
| 326 | + if (subStChild.tagName === 'VoltageLevel') { |
| 327 | + const subStName = `${subStChild.getAttribute('name') ?? 'UNKNOWN_INST'}`; |
| 328 | + |
| 329 | + const id = `${subStChild.tagName}: ${subStName}`; |
| 330 | + children[id] = voltLvObject(subStChild, leafNode); |
| 331 | + children[id]!.text = subStName; |
| 332 | + } else if (subStChild.tagName === 'PowerTransformer') { |
| 333 | + const subStName = `${subStChild.getAttribute('name') ?? 'UNKNOWN_INST'}`; |
| 334 | + |
| 335 | + const id = `${subStChild.tagName}: ${subStName}`; |
| 336 | + children[id] = condEqObject(subStChild, leafNode); |
| 337 | + children[id]!.text = subStName; |
| 338 | + } else { |
| 339 | + const funcName = `${subStChild.getAttribute('name') ?? 'UNKNOWN_INST'}`; |
| 340 | + |
| 341 | + const id = `${subStChild.tagName}: ${funcName}`; |
| 342 | + children[id] = funcObject(subStChild, leafNode); |
| 343 | + children[id]!.text = funcName; |
| 344 | + } |
| 345 | + }); |
| 346 | + |
| 347 | + tree.children = children; |
| 348 | + |
| 349 | + return tree; |
| 350 | +} |
| 351 | + |
| 352 | +export function dataAttributeTree(doc: XMLDocument, leafNode?: string): Tree { |
| 353 | + const tree: Tree = {}; |
| 354 | + |
| 355 | + getChildren(doc.querySelector('SCL')!).forEach(subStChild => { |
| 356 | + const subStName = subStChild.getAttribute('name') ?? 'UNKNOWN_LDEVICE'; |
| 357 | + const id = `Substation: ${subStName}`; |
| 358 | + tree[id] = subStObject(subStChild, leafNode); |
| 359 | + tree[id]!.text = subStName; |
| 360 | + }); |
| 361 | + |
| 362 | + return tree; |
| 363 | +} |
| 364 | + |
| 365 | +export function getSourceDef(paths: string[][]): string[] { |
| 366 | + const sourceRefs: string[] = []; |
| 367 | + |
| 368 | + for (const path of paths) { |
| 369 | + let source: string = ''; |
| 370 | + const leaf = path[path.length - 1]; |
| 371 | + const [leafTag] = leaf.split(': '); |
| 372 | + // eslint-disable-next-line no-continue |
| 373 | + if (leafTag !== 'LEAF') continue; |
| 374 | + |
| 375 | + for (const section of path) { |
| 376 | + const [tag, name] = section.split(': '); |
| 377 | + if (!['DA', 'SDO', 'BDA', 'LEAF'].includes(tag)) source += `/${name}`; |
| 378 | + else source += `.${name}`; |
| 379 | + } |
| 380 | + |
| 381 | + sourceRefs.push(source.slice(1)); |
| 382 | + } |
| 383 | + |
| 384 | + return sourceRefs; |
| 385 | +} |
0 commit comments