@Override
    public void endElement(final String uri, final String localName, final String qName) throws SAXException {
        LOGGER.trace("endElement {} {} {} level:{}", uri, localName, qName, depthLevel);

        insideElement = false;
        if (VALUE_ELEMENT.equalsIgnoreCase(localName)) {
            handleValueEndElement();
        }

        if (insideValueElement) {
            String newUri = uri;
            String newQName = qName;
            if (uri.equals(valueXmlDefaultNamespaceUri)) {
                // This is the default namespace so remove it from the element
                newQName = localName;
                newUri = "";
            }
            fastInfosetEndElement(localName, newUri, newQName);
        } else {
            if (MAP_ELEMENT.equalsIgnoreCase(localName)) {
                // capture the name of the map that the subsequent values will belong to. A ref
                // stream can contain data for multiple maps
                mapName = content.toString();
            } else if (KEY_ELEMENT.equalsIgnoreCase(localName)) {
                // the key for the KV pair
                key = content.toString();
            } else if (FROM_ELEMENT.equalsIgnoreCase(localName)) {
                // the start key for the key range
                final String string = content.toString();
                try {
                    rangeFrom = Long.parseLong(string);
                } catch (final RuntimeException e) {
                    errorReceiverProxy.log(Severity.ERROR, null, getElementId(),
                            "Unable to parse string \"" + string + "\" as long for range from", e);
                }
            } else if (TO_ELEMENT.equalsIgnoreCase(localName)) {
                // the end key for the key range
                final String string = content.toString();
                try {
                    rangeTo = Long.parseLong(string);
                } catch (final RuntimeException e) {
                    errorReceiverProxy.log(Severity.ERROR, null, getElementId(),
                            "Unable to parse string \"" + string + "\" as long for range to", e);
                }

            } else if (REFERENCE_ELEMENT.equalsIgnoreCase(localName)) {
                handleReferenceEndElement();
            }
        }

        content.clear();

        // Manually call endPrefixMapping for those prefixes we added
        final Set<String> manuallyAddedPrefixes = manuallyAddedLevelToPrefixMap.getOrDefault(
                depthLevel,
                Collections.emptySet());

        if (!manuallyAddedPrefixes.isEmpty()) {
            LOGGER.trace(() ->
                    LogUtil.message("Ending {} manually added prefixes at level {}",
                            manuallyAddedPrefixes.size(),
                            depthLevel));

            // Can't use .forEach() due to the SaxException
            for (final String manuallyAddedPrefix : manuallyAddedPrefixes) {
                fastInfosetEndPrefixMapping(manuallyAddedPrefix);
            }

            // We are leaving this level so can now delete the prefix mappings for this level
            manuallyAddedLevelToPrefixMap.get(depthLevel)
                    .clear();
        }

        super.endElement(uri, localName, qName);

        // Leaving this level so
        depthLevel--;
    }
This method applies a post processing function if we are currently within
a function element. At this stage we should have details of the function
to apply from the corresponding start element and content to apply it to
from the characters event.

@param uri       The element's Namespace URI, or the empty string.
@param localName The element's local name, or the empty string.
@param qName     The element's qualified (prefixed) name, or the empty string.
@throws org.xml.sax.SAXException The client may throw an exception during processing.
@see stroom.pipeline.filter.AbstractXMLFilter#endElement(java.lang.String,
java.lang.String, java.lang.String)
stroom-pipeline/src/main/java/stroom/pipeline/refdata/ReferenceDataFilter.java