Iterating XmlElement and calling removeChildElement

I hit a little speed bump that I figured out a workaround for, but curious if I’m overlooking a simpler solution.

In short, I was iterating over an XmlElement using forEachXmlChildElement, checking each child element for a condition, and then possibly removing the child element. This was crashing when it would loop through the next iteration, following an iteration where removeChildElement was called (if I remember right, the next child pointer wasn’t null, but its attributes were).

Anyways, my solution was to make a copy of the XmlElement to iterate, but then reference the original XmlElement when I needed to remove a child element. Like so:

// ...XmlElement presetXml was passed into this method
auto const presetXmlCopy = presetXml;  // make a copy to iterate over, that we won't modify
forEachXmlChildElement(presetXmlCopy, child)
{
    String paramId = child->getStringAttribute("id");

    // check if param exists in APVTS
    if (auto param = parameters.getParameter(paramId))
    {
        DBG("Parameter " + paramId + " found in APVTS. Keep it.");
    }
    else
    {
        DBG("Parameter " + paramId + " NOT found in APVTS! Remove it.");

        // remove child from ORIGINAL presetXml element,
        if (auto badChild = presetXml.getChildByAttribute("id", paramId))
        {
            presetXml.removeChildElement (badChild, true);
        }
}

(This is in the context of a plug-in, to sanitize XML preset files that a user can save to/load from disk, and remove any PARAM child elements from the XML with unknown ids.)

This works fine as-is, but it relies on identifying the child to remove by using getChildByAttribute. So it means I can’t remove child elements by non-unique criteria, like
if ( ! child->hasTagName("PARAM"))

Is there a way to remove child elements from an XmlElement while iterating over it?

The two approaches I know are either iterating in reverse order, or collect all children and delete them afterwards in one go:

Array<XmlElement*> toBeDeleted;

forEachXmlChildElement(presetXmlCopy, child)
{
    const auto paramId = child->getStringAttribute("id");

    if (parameters.getParameter (paramId) == nullptr)
        toBeDeleted.add (presetXml.getChildByAttribute ("id", paramId));
}

for (auto* badChild : toBeDeleted)
    presetXml.removeChildElement (badChild, true);
2 Likes

Oh, yeah, that probably makes more sense to make an array of all the ones you want removed, and collecting their pointers directly, rather than identify them by an attribute like I was.

It definitely opens up the possibility of checking if ( ! child->hasTagName("PARAM")). Thanks!