Skip to content

Commit 7e25149

Browse files
feat(menu/compareied): ignore attributes/elements when comparing IED Elements (openscd#926)
* Feature: Added the possibility to ignore attributes and/or elements when checking for differences between 2 elements. Signed-off-by: Pascal Wilbrink <[email protected]> * Cleanup of code Signed-off-by: Pascal Wilbrink <[email protected]> * Reverted package-lock.json Signed-off-by: Pascal Wilbrink <[email protected]> * Refactored and renamed some variables Signed-off-by: Pascal Wilbrink <[email protected]> * Fixed code review comments Signed-off-by: Pascal Wilbrink <[email protected]> * Fixed translation in test Signed-off-by: Pascal Wilbrink <[email protected]> * 841 Fix review comment Signed-off-by: Pascal Wilbrink <[email protected]> Signed-off-by: Pascal Wilbrink <[email protected]>
1 parent 011eec8 commit 7e25149

File tree

10 files changed

+1918
-58
lines changed

10 files changed

+1918
-58
lines changed

src/foundation/compare.ts

Lines changed: 195 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,69 @@ export type Diff<T> =
1414
| { oldValue: null; newValue: T }
1515
| { oldValue: T; newValue: T };
1616

17+
/**
18+
* Type to filter out a difference based on `tagName`.`attributeName`
19+
*
20+
* The matcher can be a boolean or a `consumer` that returns a boolean
21+
*/
22+
export interface DiffFilter<T> {
23+
[selector: string]: DiffFilterSelector<T>;
24+
};
25+
26+
interface DiffFilterSelector<T> {
27+
full?: DiffFilterConsumer<T>;
28+
attributes?: {
29+
[name: string]: DiffFilterConsumer<T>;
30+
};
31+
}
32+
33+
/**
34+
* Consumer to match if a diff should be filtered out.
35+
*/
36+
type DiffFilterConsumer<T> = boolean | ((value: T | null) => boolean);
37+
38+
function getDiffFilterSelector(
39+
elementToBeCompared: Element,
40+
rootElementToBeCompared: Element,
41+
filters: DiffFilter<Element>
42+
): DiffFilterSelector<Element> | undefined {
43+
const querySelector: string | undefined =
44+
rootElementToBeCompared === elementToBeCompared
45+
? ':scope'
46+
: Object.keys(filters).find(selector =>
47+
Array.from(
48+
rootElementToBeCompared.querySelectorAll(selector)
49+
).includes(elementToBeCompared)
50+
);
51+
52+
return querySelector ? filters[querySelector!] : undefined;
53+
}
54+
55+
function shouldFilterElement(
56+
element: Element,
57+
filter: DiffFilterSelector<Element> | undefined
58+
): boolean {
59+
if (!filter || !filter.full) {
60+
return false;
61+
}
62+
const consumer: DiffFilterConsumer<Element> = filter!.full!;
63+
64+
return typeof consumer === 'boolean' ? consumer : consumer(element);
65+
}
66+
67+
function shouldFilterAttribute(
68+
element: Element,
69+
attribute: string,
70+
filter: DiffFilterSelector<Element> | undefined
71+
): boolean {
72+
if (!filter || !filter.attributes || !filter.attributes![attribute]) {
73+
return false;
74+
}
75+
const consumer: DiffFilterConsumer<Element> = filter!.attributes![attribute];
76+
77+
return typeof consumer === 'boolean' ? consumer : consumer(element);
78+
}
79+
1780
/**
1881
* Returns the description of the Element that differs.
1982
*
@@ -32,8 +95,11 @@ function describe(element: Element): string {
3295
*/
3396
export function diffSclAttributes(
3497
elementToBeCompared: Element,
35-
elementToCompareAgainst: Element
98+
elementToCompareAgainst: Element,
99+
filterToIgnore: DiffFilter<Element>,
100+
searchElementToBeCompared: Element,
36101
): [string, Diff<string>][] {
102+
37103
const attrDiffs: [string, Diff<string>][] = [];
38104

39105
// First check if there is any text inside the element and there should be no child elements.
@@ -44,7 +110,18 @@ export function diffSclAttributes(
44110
elementToCompareAgainst.childElementCount === 0 &&
45111
newText !== oldText
46112
) {
47-
attrDiffs.push(['value', { newValue: newText, oldValue: oldText }]);
113+
const shouldFilter: boolean = shouldFilterElement(
114+
elementToBeCompared,
115+
getDiffFilterSelector(
116+
elementToBeCompared,
117+
searchElementToBeCompared,
118+
filterToIgnore
119+
)
120+
);
121+
122+
if (!shouldFilter) {
123+
attrDiffs.push(['value', { newValue: newText, oldValue: oldText }]);
124+
}
48125
}
49126

50127
// Next check if there are any difference between attributes.
@@ -54,9 +131,19 @@ export function diffSclAttributes(
54131
.concat(elementToBeCompared.getAttributeNames())
55132
);
56133
for (const name of attributeNames) {
134+
const shouldFilter: boolean = shouldFilterAttribute(
135+
elementToBeCompared,
136+
name,
137+
getDiffFilterSelector(
138+
elementToBeCompared,
139+
searchElementToBeCompared,
140+
filterToIgnore
141+
)
142+
);
57143
if (
144+
!shouldFilter &&
58145
elementToCompareAgainst.getAttribute(name) !==
59-
elementToBeCompared.getAttribute(name)
146+
elementToBeCompared.getAttribute(name)
60147
) {
61148
attrDiffs.push([
62149
name,
@@ -111,30 +198,54 @@ export function isSame(newValue: Element, oldValue: Element): boolean {
111198
*/
112199
export function diffSclChilds(
113200
elementToBeCompared: Element,
114-
elementToCompareAgainst: Element
201+
elementToCompareAgainst: Element,
202+
filterToIgnore: DiffFilter<Element>,
203+
searchElementToBeCompared: Element,
204+
searchElementToCompareAgainst: Element
115205
): Diff<Element>[] {
116206
const childDiffs: Diff<Element>[] = [];
117207
const childrenToBeCompared = Array.from(elementToBeCompared.children);
118208
const childrenToCompareTo = Array.from(elementToCompareAgainst.children);
119209

120210
childrenToBeCompared.forEach(newElement => {
121211
if (!newElement.closest('Private')) {
122-
const twinIndex = childrenToCompareTo.findIndex(ourChild =>
123-
isSame(newElement, ourChild)
212+
const shouldFilter: boolean = shouldFilterElement(
213+
newElement,
214+
getDiffFilterSelector(
215+
newElement,
216+
searchElementToBeCompared,
217+
filterToIgnore
218+
)
124219
);
125-
const oldElement = twinIndex > -1 ? childrenToCompareTo[twinIndex] : null;
220+
if (!shouldFilter) {
221+
const twinIndex = childrenToCompareTo.findIndex(ourChild =>
222+
isSame(newElement, ourChild)
223+
);
224+
const oldElement =
225+
twinIndex > -1 ? childrenToCompareTo[twinIndex] : null;
126226

127-
if (oldElement) {
128-
childrenToCompareTo.splice(twinIndex, 1);
129-
childDiffs.push({ newValue: newElement, oldValue: oldElement });
130-
} else {
131-
childDiffs.push({ newValue: newElement, oldValue: null });
227+
if (oldElement) {
228+
childrenToCompareTo.splice(twinIndex, 1);
229+
childDiffs.push({ newValue: newElement, oldValue: oldElement });
230+
} else {
231+
childDiffs.push({ newValue: newElement, oldValue: null });
232+
}
132233
}
133234
}
134235
});
135236
childrenToCompareTo.forEach(oldElement => {
136237
if (!oldElement.closest('Private')) {
137-
childDiffs.push({ newValue: null, oldValue: oldElement });
238+
const shouldFilter: boolean = shouldFilterElement(
239+
oldElement,
240+
getDiffFilterSelector(
241+
oldElement,
242+
searchElementToCompareAgainst,
243+
filterToIgnore
244+
)
245+
);
246+
if (!shouldFilter) {
247+
childDiffs.push({ newValue: null, oldValue: oldElement });
248+
}
138249
}
139250
});
140251
return childDiffs;
@@ -149,23 +260,50 @@ export function diffSclChilds(
149260
*/
150261
export function renderDiff(
151262
elementToBeCompared: Element,
152-
elementToCompareAgainst: Element
263+
elementToCompareAgainst: Element,
264+
filterToIgnore: DiffFilter<Element> = {}
265+
): TemplateResult | null {
266+
return renderDiffInternal(
267+
elementToBeCompared,
268+
elementToCompareAgainst,
269+
filterToIgnore,
270+
elementToBeCompared,
271+
elementToCompareAgainst
272+
);
273+
}
274+
275+
function renderDiffInternal(
276+
elementToBeCompared: Element,
277+
elementToCompareAgainst: Element,
278+
filterToIgnore: DiffFilter<Element> = {},
279+
searchElementToBeCompared: Element,
280+
searchElementToCompareAgainst: Element
153281
): TemplateResult | null {
154282
// Determine the ID from the current tag. These can be numbers or strings.
155283
let idTitle: string | undefined = identity(elementToBeCompared).toString();
156284
if (idTitle === 'NaN') {
157285
idTitle = undefined;
158286
}
159287

160-
// First get all differences in attributes and text for the current 2 elements.
288+
// Set the root elements if they are not defined yet
289+
searchElementToBeCompared = searchElementToBeCompared || elementToBeCompared;
290+
searchElementToCompareAgainst =
291+
searchElementToCompareAgainst || elementToCompareAgainst;
292+
161293
const attrDiffs: [string, Diff<string>][] = diffSclAttributes(
162294
elementToBeCompared,
163-
elementToCompareAgainst
295+
elementToCompareAgainst,
296+
filterToIgnore,
297+
searchElementToBeCompared,
164298
);
299+
165300
// Next check which elements are added, deleted or in both elements.
166301
const childDiffs: Diff<Element>[] = diffSclChilds(
167302
elementToBeCompared,
168-
elementToCompareAgainst
303+
elementToCompareAgainst,
304+
filterToIgnore,
305+
searchElementToBeCompared,
306+
searchElementToCompareAgainst
169307
);
170308

171309
const childAddedOrDeleted: Diff<Element>[] = [];
@@ -180,7 +318,15 @@ export function renderDiff(
180318

181319
// These children exist in both old and new element, let's check if there are any difference in the children.
182320
const childToCompareTemplates = childToCompare
183-
.map(diff => renderDiff(diff.newValue!, diff.oldValue!))
321+
.map(diff =>
322+
renderDiffInternal(
323+
diff.newValue!,
324+
diff.oldValue!,
325+
filterToIgnore,
326+
searchElementToBeCompared,
327+
searchElementToCompareAgainst
328+
)
329+
)
184330
.filter(result => result !== null);
185331

186332
// If there are difference generate the HTML otherwise just return null.
@@ -189,21 +335,26 @@ export function renderDiff(
189335
attrDiffs.length > 0 ||
190336
childAddedOrDeleted.length > 0
191337
) {
192-
return html` ${attrDiffs.length > 0 || childAddedOrDeleted.length > 0
193-
? html` <mwc-list multi>
194-
${attrDiffs.length > 0
195-
? html` <mwc-list-item noninteractive ?twoline=${!!idTitle}>
338+
return html` ${
339+
attrDiffs.length > 0 || childAddedOrDeleted.length > 0
340+
? html` <mwc-list multi>
341+
${
342+
attrDiffs.length > 0
343+
? html` <mwc-list-item noninteractive ?twoline=${!!idTitle}>
196344
<span class="resultTitle">
197345
${translate('compare.attributes', {
198346
elementName: elementToBeCompared.tagName,
199347
})}
200348
</span>
201-
${idTitle
202-
? html`<span slot="secondary">${idTitle}</span>`
203-
: nothing}
349+
${
350+
idTitle
351+
? html`<span slot="secondary">${idTitle}</span>`
352+
: nothing
353+
}
204354
</mwc-list-item>
205355
<li padded divider role="separator"></li>`
206-
: ''}
356+
: ''
357+
}
207358
${repeat(
208359
attrDiffs,
209360
e => e,
@@ -220,38 +371,45 @@ export function renderDiff(
220371
</mwc-icon>
221372
</mwc-list-item>`
222373
)}
223-
${childAddedOrDeleted.length > 0
224-
? html` <mwc-list-item noninteractive ?twoline=${!!idTitle}>
374+
${
375+
childAddedOrDeleted.length > 0
376+
? html` <mwc-list-item noninteractive ?twoline=${!!idTitle}>
225377
<span class="resultTitle">
226378
${translate('compare.children', {
227379
elementName: elementToBeCompared.tagName,
228380
})}
229381
</span>
230-
${idTitle
231-
? html`<span slot="secondary">${idTitle}</span>`
232-
: nothing}
382+
${
383+
idTitle
384+
? html`<span slot="secondary">${idTitle}</span>`
385+
: nothing
386+
}
233387
</mwc-list-item>
234388
<li padded divider role="separator"></li>`
235-
: ''}
389+
: ''
390+
}
236391
${repeat(
237392
childAddedOrDeleted,
238393
e => e,
239394
diff =>
240395
html` <mwc-list-item twoline left hasMeta>
241396
<span>${diff.oldValue?.tagName ?? diff.newValue?.tagName}</span>
242397
<span slot="secondary">
243-
${diff.oldValue
244-
? describe(diff.oldValue)
245-
: describe(diff.newValue)}
398+
${
399+
diff.oldValue
400+
? describe(diff.oldValue)
401+
: describe(diff.newValue)
402+
}
246403
</span>
247404
<mwc-icon slot="meta">
248405
${diff.oldValue ? 'delete' : 'add'}
249406
</mwc-icon>
250407
</mwc-list-item>`
251408
)}
252409
</mwc-list>`
253-
: ''}
410+
: ''
411+
}
254412
${childToCompareTemplates}`;
255413
}
256414
return null;
257-
}
415+
}

0 commit comments

Comments
 (0)