Skip to content

Commit fb41c54

Browse files
author
Dennis Labordus
committed
Add last test and small changes
Signed-off-by: Dennis Labordus <[email protected]>
1 parent d2a4375 commit fb41c54

File tree

3 files changed

+305
-71
lines changed

3 files changed

+305
-71
lines changed

public/conf/export-ied-parameters.json renamed to public/conf/export-ied-params.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
{
22
"comments": [
3-
"This file contains configuration for exporting IED Information to a CSV File.",
3+
"This file contains the configuration for exporting IED Information to a CSV File.",
44
"Each column can be defined below in the section \"columns\".",
55
"A Column must at least have a \"header\" defined.",
66
"",
77
"A selector can be defined to search for a Element, if no selector is defined, the IED Element is used.",
8-
"If the useOwnerDocument is set to true, the selector will be used on the whole document, otherwise the IED Element",
8+
"If the useOwnerDocument is set to true, the selector will be used on the whole document, otherwise on the IED Element",
99
"There is a variable 'iedName' being replaced before executing the selector, put this between '{{' an '}}'.",
10+
"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 Element.",
11+
"The dataAttributePath should at least contain 2 names, because the minimum is always a DO(I) followed by a DA(I) element.",
1012
"",
1113
"If a attributeName is defined that attribute will be retrieved from the elements found by the selector.",
12-
"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."
14+
"Otherwise the text content of the elements is retrieved."
1315
],
1416
"columns": [
1517
{

src/menu/ExportIEDParams.ts

Lines changed: 126 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,23 @@ import { compareNames } from '../foundation.js';
77
import { stripExtensionFromName } from '../compas/foundation.js';
88
import { get } from 'lit-translate';
99

10-
type ColumnSettings = {
10+
// Structure of the Configuration file defined by both types.
11+
type ColumnConfiguration = {
1112
header: string;
1213
attributeName?: string;
1314
selector?: string;
1415
useOwnerDocument?: boolean;
1516
dataAttributePath?: string[];
1617
};
1718

18-
export type Settings = {
19-
columns: ColumnSettings[];
19+
export type Configuration = {
20+
columns: ColumnConfiguration[];
2021
};
2122

23+
/**
24+
* Menu item to create a CSV File containing a line per IED holding the parameters of that IED.
25+
* Which columns are being returned is configured in the file 'public/conf/export-ied-params.json'.
26+
*/
2227
export default class ExportIEDParamsPlugin extends LitElement {
2328
@property() doc!: XMLDocument;
2429
@property() docName!: string;
@@ -31,6 +36,13 @@ export default class ExportIEDParamsPlugin extends LitElement {
3136
return selector.replace(/{{\s*iedName\s*}}/, iedName);
3237
}
3338

39+
/**
40+
* Find the DO/DA/BDA element with the passed name defined below the type element passed.
41+
* Depending on the type of Type element the query will search for the DO/DA/BDA element.
42+
*
43+
* @param typeElement - The type element, this can be a LNodeType, DOType or DAType element.
44+
* @param name - The name of the element to search for below the type element.
45+
*/
3446
private getDataElement(typeElement: Element, name: string): Element | null {
3547
if (typeElement.tagName === 'LNodeType') {
3648
return typeElement.querySelector(`:scope > DO[name="${name}"]`);
@@ -43,6 +55,12 @@ export default class ExportIEDParamsPlugin extends LitElement {
4355
}
4456
}
4557

58+
/**
59+
* Retrieve the value that will be added to the CSV file. If an attribute name is passed the value of the
60+
* attribute is returned. Otherwise, the textContent of the element is returned.
61+
* @param element - The element to retrieve the value from.
62+
* @param attributeName - Optional the name of the attribute.
63+
*/
4664
private getValue(
4765
element: Element,
4866
attributeName: string | undefined
@@ -53,6 +71,12 @@ export default class ExportIEDParamsPlugin extends LitElement {
5371
return element.textContent ?? '';
5472
}
5573

74+
/**
75+
* Use the DO/SDO/DA/BDA data element to search for the type element. In case of the DO/SDO a DOType is search
76+
* for and otherwise a DAType is searched for if the data element is a struct type.
77+
*
78+
* @param lastElement - The data element to retrieve its type definition.
79+
*/
5680
private getTypeElement(lastElement: Element | null): Element | null {
5781
if (lastElement) {
5882
if (['DO', 'SDO'].includes(lastElement.tagName)) {
@@ -69,12 +93,22 @@ export default class ExportIEDParamsPlugin extends LitElement {
6993
return null;
7094
}
7195

96+
/**
97+
* Search for the DO/SDO/DA/BDA element in the Template section of the document using the path array passed.
98+
* The LN element is the starting point for the search in the Template section.
99+
*
100+
* @param lnElement - The LN Element used as starting point in the Template section.
101+
* @param dataAttributePath - The list of elements to search for, the names of the elements.
102+
*/
72103
private getDataAttributeTemplateValue(
73104
lnElement: Element,
74105
dataAttributePath: string[]
75106
): string | null {
76107
// This is only useful if the element to start from is the LN(0) Element.
77-
if (['LN', 'LN0'].includes(lnElement.tagName)) {
108+
if (
109+
['LN', 'LN0'].includes(lnElement.tagName) &&
110+
dataAttributePath.length >= 2
111+
) {
78112
// Search LNodeType Element that is linked to the LN(0) Element.
79113
const type = lnElement.getAttribute('lnType');
80114
let typeElement = this.doc.querySelector(`LNodeType[id="${type}"]`);
@@ -96,42 +130,68 @@ export default class ExportIEDParamsPlugin extends LitElement {
96130
return null;
97131
}
98132

133+
/**
134+
* Search for the DAI element below the LN element using the path passed. The list of names is converted
135+
* to a CSS Selector to search for the DAI Element and its Val Element.
136+
*
137+
* @param lnElement - The LN Element used as starting point for the search.
138+
* @param dataAttributePath - The names of the DOI/SDI/DAI Elements to search for.
139+
*/
99140
private getDataAttributeInstanceValue(
100-
element: Element,
141+
lnElement: Element,
101142
dataAttributePath: string[]
102143
): string | null {
103-
const daiSelector = dataAttributePath
104-
.slice()
105-
.reverse()
106-
.map((path, index) => {
107-
if (index === 0) {
108-
return `DAI[name="${path}"]`;
109-
} else if (index === dataAttributePath.length - 1) {
110-
return `DOI[name="${path}"]`;
111-
}
112-
return `SDI[name="${path}"]`;
113-
})
114-
.reverse()
115-
.join(' > ');
116-
117-
const daiValueElement = element.querySelector(daiSelector + ' Val');
118-
if (daiValueElement) {
119-
return daiValueElement.textContent;
144+
if (
145+
['LN', 'LN0'].includes(lnElement.tagName) &&
146+
dataAttributePath.length >= 2
147+
) {
148+
const daiSelector = dataAttributePath
149+
.map((path, index) => {
150+
if (index === 0) {
151+
// The first element is always a DOI element.
152+
return `DOI[name="${path}"]`;
153+
} else if (index === dataAttributePath.length - 1) {
154+
// The last element is always a DAI element.
155+
return `DAI[name="${path}"]`;
156+
}
157+
// Every element(s) between the DOI and DAI element is always a SDI element.
158+
return `SDI[name="${path}"]`;
159+
})
160+
.join(' > ');
161+
162+
const daiValueElement = lnElement.querySelector(daiSelector + ' Val');
163+
return daiValueElement?.textContent ?? null;
120164
}
121165
return null;
122166
}
123167

168+
/**
169+
* First check if there is an instance element found (DAI) found, otherwise search in the Template section.
170+
*
171+
* @param lnElement - The LN Element used as starting point for the search.
172+
* @param dataAttributePath - The names of the DO(I)/SD(I)/DA(I) Elements to search for.
173+
*/
124174
private getDataAttributeValue(
125-
element: Element,
175+
lnElement: Element,
126176
dataAttributePath: string[]
127177
): string {
128-
let value = this.getDataAttributeInstanceValue(element, dataAttributePath);
178+
let value = this.getDataAttributeInstanceValue(
179+
lnElement,
180+
dataAttributePath
181+
);
129182
if (!value) {
130-
value = this.getDataAttributeTemplateValue(element, dataAttributePath);
183+
value = this.getDataAttributeTemplateValue(lnElement, dataAttributePath);
131184
}
132185
return value ?? '';
133186
}
134187

188+
/**
189+
* Retrieve the list of elements found by the selector or if no selector defined the IED element.
190+
*
191+
* @param iedElement - The IED element that will be used to search below if useOwnerDocument is false.
192+
* @param selector - If passed the CSS selector to search for the elements.
193+
* @param useOwnerDocument - If false will use the IED element to search below, otherwise the full document.
194+
*/
135195
private getElements(
136196
iedElement: Element,
137197
selector: string | undefined,
@@ -152,62 +212,84 @@ export default class ExportIEDParamsPlugin extends LitElement {
152212
return elements;
153213
}
154214

155-
private contentIED(settings: Settings, iedElement: Element): string[] {
156-
return settings.columns.map(value => {
215+
/**
216+
* Create a single line of values for the CSV File.
217+
*
218+
* @param configuration - The configuration with values to retrieve.
219+
* @param iedElement - The IED Element for which to retrieve the values.
220+
*/
221+
private cvsLine(configuration: Configuration, iedElement: Element): string[] {
222+
return configuration.columns.map(column => {
157223
const elements = this.getElements(
158224
iedElement,
159-
value.selector,
160-
value.useOwnerDocument ?? false
225+
column.selector,
226+
column.useOwnerDocument ?? false
161227
);
162228

163229
return elements
164230
.map(element => {
165-
if (value.dataAttributePath) {
166-
return this.getDataAttributeValue(element, value.dataAttributePath);
231+
if (column.dataAttributePath) {
232+
return this.getDataAttributeValue(
233+
element,
234+
column.dataAttributePath
235+
);
167236
}
168-
return this.getValue(element, value.attributeName);
237+
return this.getValue(element, column.attributeName);
169238
})
170239
.filter(value => value!)
171240
.join(' / ');
172241
});
173242
}
174243

175-
private content(settings: Settings): string[][] {
244+
/**
245+
* Create the full content of the CSV file, for each IED found a line of values is returned.
246+
*
247+
* @param configuration - The configuration of the values to retrieve.
248+
*/
249+
private cvsLines(configuration: Configuration): string[][] {
176250
const ieds = this.ieds;
177251
if (ieds.length > 0) {
178252
return ieds
179253
.sort(compareNames)
180-
.map(iedElement => this.contentIED(settings, iedElement));
254+
.map(iedElement => this.cvsLine(configuration, iedElement));
181255
}
182256
return [[get('compas.exportIEDParams.noIEDs')]];
183257
}
184258

185-
private columnHeaders(settings: Settings): string[] {
186-
return settings.columns.map(value => value.header);
259+
/**
260+
* Return the headers values from the configuration.
261+
*
262+
* @param configuration - The configuration containing the header names.
263+
*/
264+
private columnHeaders(configuration: Configuration): string[] {
265+
return configuration.columns.map(column => column.header);
187266
}
188267

189-
async getSettings(): Promise<Settings> {
190-
return await import('../../public/conf/export-ied-parameters.json').then(
268+
/**
269+
* Read the configuration file.
270+
*/
271+
async getConfiguration(): Promise<Configuration> {
272+
return await import('../../public/conf/export-ied-params.json').then(
191273
module => module.default
192274
);
193275
}
194276

195277
async run(): Promise<void> {
196-
// Import the JSON Configuration needed for the import.
197-
const settings = await this.getSettings();
278+
// Retrieve the JSON Configuration.
279+
const configuration = await this.getConfiguration();
198280

199-
// Create the content using a CSV Writer.
200-
const content = stringify(this.content(settings), {
281+
// Create the content using a CSV Library.
282+
const content = stringify(this.cvsLines(configuration), {
201283
header: true,
202-
columns: this.columnHeaders(settings),
284+
columns: this.columnHeaders(configuration),
203285
});
204286
const blob = new Blob([content], {
205287
type: 'text/csv',
206288
});
207289

208290
// Push the data back to the user.
209291
const a = document.createElement('a');
210-
a.download = stripExtensionFromName(this.docName) + '-ied-parameters.csv';
292+
a.download = stripExtensionFromName(this.docName) + '-ied-params.csv';
211293
a.href = URL.createObjectURL(blob);
212294
a.dataset.downloadurl = ['text/csv', a.download, a.href].join(':');
213295
a.style.display = 'none';

0 commit comments

Comments
 (0)