Skip to content

Commit d2a4375

Browse files
author
Dennis Labordus
committed
Added first tests and make small changes.
Signed-off-by: Dennis Labordus <[email protected]>
1 parent c8da6fc commit d2a4375

File tree

9 files changed

+1539
-121
lines changed

9 files changed

+1539
-121
lines changed

Dockerfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ COPY build/. /usr/share/nginx/html/
33

44
VOLUME /etc/nginx/conf.d
55
VOLUME /usr/share/nginx/html/public/cim
6+
VOLUME /usr/share/nginx/html/public/conf

public/conf/export-ied-parameters.json

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"",
77
"A selector can be defined to search for a Element, if no selector is defined, the IED Element is used.",
88
"If the useOwnerDocument is set to true, the selector will be used on the whole document, otherwise the IED Element",
9+
"There is a variable 'iedName' being replaced before executing the selector, put this between '{{' an '}}'.",
910
"",
1011
"If a attributeName is defined that attribute will be retrieved from the elements found by the selector.",
1112
"If a dataAttributePath is defined, the selector should return a LN(0) Element and the path is then used to search for a DAI/DA Value."
@@ -31,57 +32,62 @@
3132
},
3233
{
3334
"header": "IL1 Primary rated current",
34-
"selector": "AccessPoint > Server > LDevice[inst=\"LD0\"] > LN[prefix=\"IL1\"][lnClass=\"TCTR\"]",
35+
"selector": "AccessPoint > Server > LDevice > LN[prefix=\"IL1\"][lnClass=\"TCTR\"]",
3536
"dataAttributePath": ["ARtg", "setMag", "f"]
3637
},
3738
{
3839
"header": "IL1 Network Nominal Current",
39-
"selector": "AccessPoint > Server > LDevice[inst=\"LD0\"] > LN[prefix=\"IL1\"][lnClass=\"TCTR\"]",
40+
"selector": "AccessPoint > Server > LDevice > LN[prefix=\"IL1\"][lnClass=\"TCTR\"]",
4041
"dataAttributePath": ["ARtgNom", "setMag", "f"]
4142
},
4243
{
4344
"header": "IL1 Secondary rated current",
44-
"selector": "AccessPoint > Server > LDevice[inst=\"LD0\"] > LN[prefix=\"IL1\"][lnClass=\"TCTR\"]",
45-
"dataAttributePath": ["ARtgSec", "setVal"]
46-
},
47-
{
48-
"header": "IL1 Rated Secondary Value",
49-
"selector": "AccessPoint > Server > LDevice[inst=\"LD0\"] > LN[prefix=\"IL1\"][lnClass=\"TCTR\"]",
50-
"dataAttributePath": ["VRtgSec", "setMag", "f"]
45+
"selector": "AccessPoint > Server > LDevice > LN[prefix=\"IL1\"][lnClass=\"TCTR\"]",
46+
"dataAttributePath": ["ARtgSec", "stVal"]
5147
},
5248
{
5349
"header": "RES Primary rated current",
54-
"selector": "AccessPoint > Server > LDevice[inst=\"LD0\"] > LN[prefix=\"RES\"][lnClass=\"TCTR\"]",
50+
"selector": "AccessPoint > Server > LDevice > LN[prefix=\"RES\"][lnClass=\"TCTR\"]",
5551
"dataAttributePath": ["ARtg", "setMag", "f"]
5652
},
5753
{
5854
"header": "RES Network Nominal Current",
59-
"selector": "AccessPoint > Server > LDevice[inst=\"LD0\"] > LN[prefix=\"RES\"][lnClass=\"TCTR\"]",
55+
"selector": "AccessPoint > Server > LDevice > LN[prefix=\"RES\"][lnClass=\"TCTR\"]",
6056
"dataAttributePath": ["ARtgNom", "setMag", "f"]
6157
},
6258
{
6359
"header": "RES Secondary rated current",
64-
"selector": "AccessPoint > Server > LDevice[inst=\"LD0\"] > LN[prefix=\"RES\"][lnClass=\"TCTR\"]",
65-
"dataAttributePath": ["ARtgSec", "setVal"]
66-
},
67-
{
68-
"header": "RES Rated Secondary Value",
69-
"selector": "AccessPoint > Server > LDevice[inst=\"LD0\"] > LN[prefix=\"RES\"][lnClass=\"TCTR\"]",
70-
"dataAttributePath": ["VRtgSec", "setMag", "f"]
60+
"selector": "AccessPoint > Server > LDevice > LN[prefix=\"RES\"][lnClass=\"TCTR\"]",
61+
"dataAttributePath": ["ARtgSec", "stVal"]
7162
},
7263
{
7364
"header": "UL1 Primary rated voltage",
74-
"selector": "AccessPoint > Server > LDevice[inst=\"LD0\"] > LN[lnClass=\"TVTR\"]",
65+
"selector": "AccessPoint > Server > LDevice > LN[prefix=\"UL1\"][lnClass=\"TVTR\"]",
7566
"dataAttributePath": ["VRtg", "setMag", "f"]
7667
},
7768
{
7869
"header": "UL1 Secondary rated voltage",
79-
"selector": "AccessPoint > Server > LDevice[inst=\"LD0\"] > LN[lnClass=\"TVTR\"]",
80-
"dataAttributePath": ["VRtgScy", "setMag", "f"]
70+
"selector": "AccessPoint > Server > LDevice > LN[prefix=\"UL1\"][lnClass=\"TVTR\"]",
71+
"dataAttributePath": ["VRtgSec", "stVal"]
8172
},
8273
{
8374
"header": "UL1 Devision ratio",
84-
"selector": "AccessPoint > Server > LDevice[inst=\"LD0\"] > LN[lnClass=\"TVTR\"]",
75+
"selector": "AccessPoint > Server > LDevice > LN[prefix=\"UL1\"][lnClass=\"TVTR\"]",
76+
"dataAttributePath": ["Rat", "setMag", "f"]
77+
},
78+
{
79+
"header": "RES Primary rated voltage",
80+
"selector": "AccessPoint > Server > LDevice > LN[prefix=\"RES\"][lnClass=\"TVTR\"]",
81+
"dataAttributePath": ["VRtg", "setMag", "f"]
82+
},
83+
{
84+
"header": "RES Secondary rated voltage",
85+
"selector": "AccessPoint > Server > LDevice > LN[prefix=\"RES\"][lnClass=\"TVTR\"]",
86+
"dataAttributePath": ["VRtgSec", "stVal"]
87+
},
88+
{
89+
"header": "RES Devision ratio",
90+
"selector": "AccessPoint > Server > LDevice > LN[prefix=\"RES\"][lnClass=\"TVTR\"]",
8591
"dataAttributePath": ["Rat", "setMag", "f"]
8692
},
8793
{

src/menu/ExportIEDParams.ts

Lines changed: 102 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -5,55 +5,53 @@ import { stringify } from 'csv-stringify/browser/esm/sync';
55
import { compareNames } from '../foundation.js';
66

77
import { stripExtensionFromName } from '../compas/foundation.js';
8+
import { get } from 'lit-translate';
89

9-
import settings from '../../public/conf/export-ied-parameters.json';
10+
type ColumnSettings = {
11+
header: string;
12+
attributeName?: string;
13+
selector?: string;
14+
useOwnerDocument?: boolean;
15+
dataAttributePath?: string[];
16+
};
1017

11-
function getDataElement(typeElement: Element, name: string): Element | null {
12-
if (typeElement.tagName === 'LNodeType') {
13-
return typeElement.querySelector(`:scope > DO[name="${name}"]`);
14-
} else if (typeElement.tagName === 'DOType') {
15-
return typeElement.querySelector(
16-
`:scope > SDO[name="${name}"], :scope > DA[name="${name}"]`
17-
);
18-
} else {
19-
return typeElement.querySelector(`:scope > BDA[name="${name}"]`);
20-
}
21-
}
18+
export type Settings = {
19+
columns: ColumnSettings[];
20+
};
2221

23-
function getValue(element: Element, attributeName: string | undefined): string {
24-
if (attributeName) {
25-
return element.getAttribute(attributeName) ?? '';
22+
export default class ExportIEDParamsPlugin extends LitElement {
23+
@property() doc!: XMLDocument;
24+
@property() docName!: string;
25+
26+
get ieds(): Element[] {
27+
return Array.from(this.doc.querySelectorAll(`IED`));
2628
}
27-
return element.textContent ?? '';
28-
}
2929

30-
function getSelector(selector: string, iedName: string) {
31-
return selector.replace('{{ iedName }}', iedName);
32-
}
30+
private getSelector(selector: string, iedName: string) {
31+
return selector.replace(/{{\s*iedName\s*}}/, iedName);
32+
}
3333

34-
function getElements(
35-
iedElement: Element,
36-
selector: string | undefined,
37-
useOwnerDocument: boolean
38-
): Element[] {
39-
let elements: Element[] = [iedElement];
40-
if (selector) {
41-
const iedName = iedElement.getAttribute('name') ?? '';
42-
const substitutedSelector = getSelector(selector, iedName);
43-
if (useOwnerDocument) {
44-
elements = Array.from(
45-
iedElement.ownerDocument.querySelectorAll(substitutedSelector)
34+
private getDataElement(typeElement: Element, name: string): Element | null {
35+
if (typeElement.tagName === 'LNodeType') {
36+
return typeElement.querySelector(`:scope > DO[name="${name}"]`);
37+
} else if (typeElement.tagName === 'DOType') {
38+
return typeElement.querySelector(
39+
`:scope > SDO[name="${name}"], :scope > DA[name="${name}"]`
4640
);
4741
} else {
48-
elements = Array.from(iedElement.querySelectorAll(substitutedSelector));
42+
return typeElement.querySelector(`:scope > BDA[name="${name}"]`);
4943
}
5044
}
51-
return elements;
52-
}
5345

54-
export default class ExportIEDParametersPlugin extends LitElement {
55-
@property() doc!: XMLDocument;
56-
@property() docName!: string;
46+
private getValue(
47+
element: Element,
48+
attributeName: string | undefined
49+
): string {
50+
if (attributeName) {
51+
return element.getAttribute(attributeName) ?? '';
52+
}
53+
return element.textContent ?? '';
54+
}
5755

5856
private getTypeElement(lastElement: Element | null): Element | null {
5957
if (lastElement) {
@@ -72,20 +70,20 @@ export default class ExportIEDParametersPlugin extends LitElement {
7270
}
7371

7472
private getDataAttributeTemplateValue(
75-
element: Element,
73+
lnElement: Element,
7674
dataAttributePath: string[]
7775
): string | null {
7876
// This is only useful if the element to start from is the LN(0) Element.
79-
if (['LN', 'LN0'].includes(element.tagName)) {
77+
if (['LN', 'LN0'].includes(lnElement.tagName)) {
8078
// Search LNodeType Element that is linked to the LN(0) Element.
81-
const type = element.getAttribute('lnType');
79+
const type = lnElement.getAttribute('lnType');
8280
let typeElement = this.doc.querySelector(`LNodeType[id="${type}"]`);
8381
let lastElement: Element | null = null;
8482

8583
// Now start search through the Template section jumping between the type elements.
8684
dataAttributePath.forEach(name => {
8785
if (typeElement) {
88-
lastElement = getDataElement(typeElement, name);
86+
lastElement = this.getDataElement(typeElement, name);
8987
typeElement = this.getTypeElement(lastElement);
9088
}
9189
});
@@ -134,46 +132,80 @@ export default class ExportIEDParametersPlugin extends LitElement {
134132
return value ?? '';
135133
}
136134

137-
private content(): string[][] {
138-
return Array.from(this.doc.querySelectorAll(`IED`))
139-
.sort(compareNames)
140-
.map(iedElement => {
141-
return settings.columns.map(value => {
142-
const elements = getElements(
143-
iedElement,
144-
value.selector,
145-
value.useOwnerDocument ?? false
146-
);
147-
148-
return elements
149-
.map(element => {
150-
if (value.dataAttributePath) {
151-
return this.getDataAttributeValue(
152-
element,
153-
value.dataAttributePath
154-
);
155-
}
156-
return getValue(element, value.attributeName);
157-
})
158-
.filter(value => value!)
159-
.join(' / ');
160-
});
161-
});
135+
private getElements(
136+
iedElement: Element,
137+
selector: string | undefined,
138+
useOwnerDocument: boolean
139+
): Element[] {
140+
let elements: Element[] = [iedElement];
141+
if (selector) {
142+
const iedName = iedElement.getAttribute('name') ?? '';
143+
const substitutedSelector = this.getSelector(selector, iedName);
144+
if (useOwnerDocument) {
145+
elements = Array.from(
146+
iedElement.ownerDocument.querySelectorAll(substitutedSelector)
147+
);
148+
} else {
149+
elements = Array.from(iedElement.querySelectorAll(substitutedSelector));
150+
}
151+
}
152+
return elements;
153+
}
154+
155+
private contentIED(settings: Settings, iedElement: Element): string[] {
156+
return settings.columns.map(value => {
157+
const elements = this.getElements(
158+
iedElement,
159+
value.selector,
160+
value.useOwnerDocument ?? false
161+
);
162+
163+
return elements
164+
.map(element => {
165+
if (value.dataAttributePath) {
166+
return this.getDataAttributeValue(element, value.dataAttributePath);
167+
}
168+
return this.getValue(element, value.attributeName);
169+
})
170+
.filter(value => value!)
171+
.join(' / ');
172+
});
173+
}
174+
175+
private content(settings: Settings): string[][] {
176+
const ieds = this.ieds;
177+
if (ieds.length > 0) {
178+
return ieds
179+
.sort(compareNames)
180+
.map(iedElement => this.contentIED(settings, iedElement));
181+
}
182+
return [[get('compas.exportIEDParams.noIEDs')]];
162183
}
163184

164-
private columnHeaders(): string[] {
185+
private columnHeaders(settings: Settings): string[] {
165186
return settings.columns.map(value => value.header);
166187
}
167188

189+
async getSettings(): Promise<Settings> {
190+
return await import('../../public/conf/export-ied-parameters.json').then(
191+
module => module.default
192+
);
193+
}
194+
168195
async run(): Promise<void> {
169-
const content = stringify(this.content(), {
196+
// Import the JSON Configuration needed for the import.
197+
const settings = await this.getSettings();
198+
199+
// Create the content using a CSV Writer.
200+
const content = stringify(this.content(settings), {
170201
header: true,
171-
columns: this.columnHeaders(),
202+
columns: this.columnHeaders(settings),
172203
});
173204
const blob = new Blob([content], {
174205
type: 'text/csv',
175206
});
176207

208+
// Push the data back to the user.
177209
const a = document.createElement('a');
178210
a.download = stripExtensionFromName(this.docName) + '-ied-parameters.csv';
179211
a.href = URL.createObjectURL(blob);

src/translations/de.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ export const de: Translations = {
745745
nodataset: 'Kein verbundener Datensatz',
746746
},
747747
userinfo: {
748-
loggedInAs: '???'
748+
loggedInAs: '???',
749749
},
750750
add: 'Hinzufügen',
751751
new: 'Neu',
@@ -773,7 +773,7 @@ export const de: Translations = {
773773
notExists: '???',
774774
noSclTypes: '???',
775775
noScls: '???',
776-
noSclVersions: "???",
776+
noSclVersions: '???',
777777
comment: '???',
778778
error: {
779779
type: '???',
@@ -786,7 +786,7 @@ export const de: Translations = {
786786
patch: '???',
787787
},
788788
import: {
789-
title: '???'
789+
title: '???',
790790
},
791791
open: {
792792
title: '???',
@@ -830,8 +830,8 @@ export const de: Translations = {
830830
title: '???',
831831
sclInfo: '???: {{name}}, ???: {{version}}',
832832
addVersionButton: '???',
833-
confirmRestoreTitle: "???",
834-
confirmRestore: "??? {{version}}?",
833+
confirmRestoreTitle: '???',
834+
confirmRestore: '??? {{version}}?',
835835
restoreVersionSuccess: '??? {{version}}',
836836
deleteProjectButton: '???',
837837
confirmDeleteTitle: '???',
@@ -840,13 +840,13 @@ export const de: Translations = {
840840
confirmDeleteVersionTitle: '???',
841841
confirmDeleteVersion: '??? {{version}}?',
842842
deleteVersionSuccess: '??? {{version}}',
843-
confirmButton: "???",
844-
compareButton: "???",
845-
selectTwoVersionsTitle: "???",
846-
selectTwoVersionsMessage: "???",
843+
confirmButton: '???',
844+
compareButton: '???',
845+
selectTwoVersionsTitle: '???',
846+
selectTwoVersionsMessage: '???',
847847
compareCurrentButton: '???',
848-
selectOneVersionsTitle: "???",
849-
selectOneVersionsMessage: "???",
848+
selectOneVersionsTitle: '???',
849+
selectOneVersionsMessage: '???',
850850
},
851851
scl: {
852852
wizardTitle: '???',
@@ -865,6 +865,9 @@ export const de: Translations = {
865865
sclAutoAlignmentServiceUrl: 'CoMPAS SCL Auto Alignment Service URL',
866866
useWebsockets: '???',
867867
},
868+
exportIEDParams: {
869+
noIEDs: '???',
870+
},
868871
session: {
869872
headingExpiring: '???',
870873
explainExpiring: '???',

0 commit comments

Comments
 (0)