xmlpowershell

Remove multiple XML elements


Given an XmlElement with various child nodes, and a $invalidElements array of invalid child node names, I want to delete all the offending child nodes. I started with this

foreach ($childNode in $sourceElement.ChildNodes) {
    if ($childNode.LocalName -in $invalidElements) {
        [Void]$childNode.ParentNode.RemoveChild($childNode)
    }
}

And it works up to the first delete, with the loop ending then. Because I am mutating the object I am trying to loop through. So I wondering what the most efficient way to do this is. $invalidElements is an Array from a HashSet, so duplicate invalid elements aren't represented, since this was initially intended for logging only. My thought is to create a new array that contains all the invalid names. Then I can loop While that array is populated, and inside that I can loop until the first instance of an invalid element, then delete the element and an instance of the name from the array. But that seems ugly. Seems to me there has to be a more elegant way of doing this?

EDIT: So, an alternative to what @mathias-r-jessen posted. I'll need to decide which of the two I like better. :)

$elementsToRemove = $sourceElement.ChildNodes.Where({$_.LocalName -in $invalidElements})
foreach ($elementToRemove in $elementsToRemove) {
    [Void]$elementToRemove.ParentNode.RemoveChild($elementToRemove)
}

Solution

  • As you've found yourself, you can preempt invalidating the enumerator on mutation by materializing an array of child nodes ahead of time and then iterate over that instead - the easiest way to do so is probably to evaluate .ChildNodes in a subarray expression @():

    foreach ($childNode in @($sourceElement.ChildNodes)) {
        if ($childNode.LocalName -in $invalidElements) {
            [Void]$childNode.ParentNode.RemoveChild($childNode)
        }
    }
    

    While modifying the collection underpinning $sourceElement.ChildNodes would still invalidate any enumerator obtained through $sourceElement.ChildNodes, it won't make a difference to the array created and populated by @(...)