@Override
    public void endElement(String uri, String localName, String qName)
            throws SAXException {
        if ("p" == localName && NS == uri) {
            return;
        }
        switch (state) {
            case AWAITING_HEADING:
                break;
            case IN_H4:
                if ("h4" == localName && NS == uri) {
                    Matcher m = ELEMENT.matcher(referenceText);
                    if (m.matches()) {
                        String ln = nameText.toString().intern();
                        if ("" == ln) {
                            throw new SAXParseException(
                                    "Malformed spec: no element"+currentName, locator);
                        }
                            if (currentId == null) {
                                state = State.AWAITING_HEADING;
//                                throw new SAXParseException(
//                                        "Malformed spec: no element id.", locator);
                            }
                            state = State.AWAITING_ELEMENT_DL;
                    } else {
                        currentId = null;
                        nameText.setLength(0);
                        state = State.AWAITING_HEADING;
                    }
                }
                break;
            case IN_CODE_IN_H4:
                if ("code" == localName && NS == uri) {
                    state = State.IN_H4;
                }
                break;
            case AWAITING_ELEMENT_DL:
                break;
            case IN_ELEMENT_DL_START:
                throw new SAXParseException(
                        "Malformed spec: no children in element dl.", locator);
            case IN_CATEGORIES_DT:
                if ("a" == localName && NS == uri) {
                    Matcher m = CATEGORIES.matcher(referenceText);
                    if (m.matches()) {
                        state = State.CAPTURING_CATEGORIES_DDS;
                        captureDepth = 0;
                        fragmentBuilder = new TreeBuilder(true, true);
                    } else {
                        throw new SAXParseException(
                                "Malformed spec: Expected dt to be categories dt but it was not.", locator);
                    }
                }
                break;
            case IN_CONTEXT_DT:
                if ("a" == localName && NS == uri) {
                    Matcher m = CONTEXT.matcher(referenceText);
                    if (m.matches()) {
                        state = State.CAPTURING_CONTEXT_DDS;
                        captureDepth = 0;
                        fragmentBuilder = new TreeBuilder(true, true);
                    } else {
                      System.err.printf("Line: %d Col: %d\n", locator.getLineNumber(), locator.getColumnNumber());
                        throw new SAXParseException(
                                "Malformed spec at element " + currentName.getLocalName() + " (" + currentId + "): Expected dt to be context dt but it was not.", locator);
                    }
                }
                break;
            case IN_CONTENT_MODEL_DT:
                if ("a" == localName && NS == uri) {
                    Matcher m = CONTENT_MODEL.matcher(referenceText);
                    if (m.matches()) {
                        state = State.CAPTURING_CONTENT_MODEL_DDS;
                        captureDepth = 0;
                        fragmentBuilder = new TreeBuilder(true, true);
                    } else {
                        throw new SAXParseException(
                                "Malformed spec: Expected dt to be content-model dt but it was not.", locator);
                    }
                }
                break;
            case IN_TAG_OMISSION_DT:
                if ("a" == localName && NS == uri) {
                    Matcher m = TAG_OMISSION.matcher(referenceText);
                    if (m.matches()) {
                        state = State.CAPTURING_TAG_OMISSION_DDS;
                        captureDepth = 0;
                        fragmentBuilder = new TreeBuilder(true, true);
                    } else {
                        throw new SAXParseException(
                                "Malformed spec: Expected dt to be tag-omission dt but it was not.", locator);
                    }
                }
                break;
            case IN_ATTRIBUTES_DT:
                if ("a" == localName && NS == uri) {
                    Matcher m = ATTRIBUTES.matcher(referenceText);
                    if (m.matches()) {
                        state = State.CAPTURING_ATTRIBUTES_DDS;
                        captureDepth = 0;
                        fragmentBuilder = new TreeBuilder(true, true);
                    } else {
                        throw new SAXParseException(
                                "Malformed spec: Expected dt to be content-attributes dt but it was not.", locator);
                    }
                }
                break;
            case CAPTURING_CATEGORIES_DDS:
            case CAPTURING_CONTEXT_DDS:
            case CAPTURING_CONTENT_MODEL_DDS:
            case CAPTURING_TAG_OMISSION_DDS:
            case CAPTURING_ATTRIBUTES_DDS:
                if ("dt" == localName) {
                    break;
                }
                if (captureDepth == 0) {
                    throw new SAXParseException(
                            "Malformed spec: Did not see following dt when capturing dds.", locator);
                }
                captureDepth--;
                fragmentBuilder.endElement(uri, localName, qName);
                break;
        }
    }
@see org.xml.sax.ContentHandler#characters(char[], int, int)
src/nu/validator/spec/html5/Html5SpecBuilder.java