Skip to content

fix: removeIED - prevent duplicate LNode's#147

Open
stee-re wants to merge 1 commit into
openscd:mainfrom
stee-re:feat_removeIED-duplicates-issue
Open

fix: removeIED - prevent duplicate LNode's#147
stee-re wants to merge 1 commit into
openscd:mainfrom
stee-re:feat_removeIED-duplicates-issue

Conversation

@stee-re

@stee-re stee-re commented May 27, 2026

Copy link
Copy Markdown
Collaborator

resolves #121

Summary

When multiple IEDs reference the same LNode, removing those IEDs sequentially can result in duplicate LNode
entries with iedName="None". This PR modifies the default behaviour for removeIED() to delete all LNodes bound to that IED by default, but exposes a new optional flag preserveLNodes which will instead, set the LNodes ideName="None". In cases where setting the iedName="None" would lead to a duplicate LNode, then it is removed instead.
E.g.

remove(myIEDElement, {preserveLNodes: true});

Changes

  • tIED/removeIED.ts:
    • Added RemoveIedOptions interface with an optional preserveLNodes flag (defaults to false)
    • Added the following LNode related functions:
    • removeBoundLNodes() - Default handling for LNodes - find any (public) matching LNodes and create a Remove edit for them.
    • detachLNodeBindings() - if "preserveLNodes flag set, this function is called instead, to instead set the LNodes iedName to "None", taking care to avoid duplicates. Any LNodes encountered which would cause a duplicate, are removed instead.

Helper functions

  • lNodeKey - creates a key for the LNode based on all the other attributes (except iedName) which combined, must be unique.
  • removeLNode() & setLNodeToNone() - oneliner functions, there only to make the code more readable.
  • getLNodeByIedName() used to find the bound LNodes (and the specification LNodes (set ot None))

Other Changed files

  • index.ts: Exported RemoveIedOptions type
  • tIED/removeIED.spec.ts: Added tests for LNode removal and opt-in LNode updating behaviour
  • tIED/removeIED.testfile.ts: Added test fixture with duplicate LNode scenario
  • .gitignore: Added IDE and build artifact patterns

Code coverage 99.52%

@stee-re stee-re force-pushed the feat_removeIED-duplicates-issue branch 2 times, most recently from ec48b77 to 4ef2497 Compare May 27, 2026 13:32
@stee-re stee-re requested review from JakobVogelsang and danyill and removed request for danyill May 27, 2026 13:32

@danyill danyill left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for looking at this and my apologies for the delay in reviewing.

I've flagged just a few small "tidyness", feel free to fix or not as you wish.

Comment thread tIED/removeIED.spec.ts
* - Find all LNode references with matching iedName and either set them to None, or delete them if setting them to None would result in duplicate LNode keys within the same scope.
* The scope is defined as the nearest Bay, VL or Substation parent.
*/
describe("without 'preservveNodes' set (default)", () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
describe("without 'preservveNodes' set (default)", () => {
describe("without 'preserveNodes' set (default)", () => {

Comment thread tIED/removeIED.spec.ts
* The scope is defined as the nearest Bay, VL or Substation parent.
*/
describe("without 'preservveNodes' set (default)", () => {
//TODO consider changing this into a forEach (Substation, VL and Bay) array test.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//TODO consider changing this into a forEach (Substation, VL and Bay) array test.

It looks like an array already...

Comment thread tIED/removeIED.spec.ts
Comment on lines +161 to +162

//

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//

Just removing a little crud.

Comment thread tIED/removeIED.spec.ts
// Do keep in mind however, the subject SCL has 2 of everything. E.g. S1 & S2
["Bay", "VoltageLevel", "Substation"].forEach((scope) => {
it(`Within a ${scope}, it sets all bound LNodes to None`, () => {
//we're using the "duplicates" test file, but by only deleting 1 IED, no duplicates occur (yet).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//we're using the "duplicates" test file, but by only deleting 1 IED, no duplicates occur (yet).
// We're using the "duplicates" test file, but by only deleting 1 IED, no duplicates occur (yet).

Comment thread tIED/removeIED.spec.ts
describe("referenced LNode's", () => {
/*
* Here we need to test:
* - Delete all LNode references found with matching iedName, BUT only inside the substation section (but not inside Private sections).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought after reading this you would have put an LNode inside a Private section to test this...

Comment thread tIED/removeIED.spec.ts

["Bay", "VoltageLevel", "Substation"].forEach((scope) => {
it(`Within a ${scope}, it removes 'would-be' duplicates`, () => {
//we're using the "duplicates" test file, but by only deleting 1 IED, no duplicates occur.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
//we're using the "duplicates" test file, but by only deleting 1 IED, no duplicates occur.
// We're using the "duplicates" test file, but by only deleting 1 IED, no duplicates occur.

Comment thread tIED/removeIED.ts
Comment on lines +78 to +80
function removeBoundLNodes(ied: Element, name: string): Remove[] {
return (getLNodesByIedName(ied.ownerDocument, name) ?? []).map(removeLNode);
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it me or is this very verbose? Or is that the AI secret sauce 😉

Why not just?

Suggested change
function removeBoundLNodes(ied: Element, name: string): Remove[] {
return (getLNodesByIedName(ied.ownerDocument, name) ?? []).map(removeLNode);
}
function removeBoundLNodes(ied: Element, name: string): Remove[] {
return (getLNodesByIedName(ied.ownerDocument, name) ?? []).map( { node: ln };);
}

Comment thread tIED/removeIED.ts
return [];
}

const UnboundLNodesByScope = new Map<Element, Set<string>>();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const UnboundLNodesByScope = new Map<Element, Set<string>>();
const unboundLNodesByScope = new Map<Element, Set<string>>();

Why would this start with a capital letter?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tIED: removeIeds behaviours with LNodes

2 participants