@Override
    public void startElement(String uri, String localName, String qName,
            Attributes atts) throws SAXException {
        if (contentHandler == null) {
            return;
        }
        if (isWalkOver) {
            contentHandler.startElement(uri, localName, qName, atts);
            return;
        }

        if ("hgroup".equals(localName)) {
            inHgroup = true;
        }

        ++currentWalkDepth;

        boolean hidden = atts.getIndex("", "hidden") >= 0
                || "template".equals(localName);
        elementStack.push(new Element(currentWalkDepth, localName, hidden));

        // If the top of the stack is a heading content element or an element
        // with a hidden attribute
        if (inHeadingContentOrHiddenElement) {
            if (!inHiddenSubtree() && "img".equals(localName)
                    && atts.getIndex("", "alt") >= 0) {
                currentSection.getHeadingImgAltTextBuilder().append(
                        atts.getValue("", "alt"));
            }
            // Do nothing.
            contentHandler.startElement(uri, localName, qName, atts);
            return;
        }

        // When entering an element with a hidden attribute
        if (hidden) {
            // Push the element being entered onto the stack. (This causes the
            // algorithm to skip that element and any descendants of the
            // element.)
            outlineStack.push(new Element(currentWalkDepth, localName, hidden));
            inHeadingContentOrHiddenElement = true;
            contentHandler.startElement(uri, localName, qName, atts);
            return;
        }

        // When entering a sectioning content element or a sectioning root
        // element
        if (Arrays.binarySearch(SECTIONING_CONTENT_ELEMENTS, localName) > -1
                || Arrays.binarySearch(SECTIONING_ROOT_ELEMENTS.toArray(),
                        localName) > -1) {
            if (currentOutlinee != null) {
                // If current outlinee is not null, and the current section has
                // no heading,
                // create an implied heading and let that be the heading for the
                // current section.
                if (currentSection != null && !currentSection.hasHeading()) {
                    currentSection.createImpliedHeading();
                }
                // If current outlinee is not null, push current outlinee onto
                // the stack.
                outlineStack.push(currentOutlinee);
            }

            // Let current outlinee be the element that is being entered.
            currentOutlinee = new Element(currentWalkDepth, localName, hidden);

            // Let current section be a newly created section for the current
            // outlinee element.
            // Associate current outlinee with current section.
            currentSection = new Section(localName);

            // Let there be a new outline for the new current outlinee,
            // initialized with just the new current section as the only section
            // in the outline.
            currentOutlinee.getOutline().add(currentSection);
            contentHandler.startElement(uri, localName, qName, atts);
            return;
        }

        // The following implements the "When entering a heading content
        // element" part of the outline algorithm in the spec, but note
        // that in the internals of our implementation, we don't handle the
        // case of hgroup here, but instead just the h1-h6 case.
        if (Arrays.binarySearch(H1_TO_H6_ELEMENTS, localName) > -1
                && currentOutlinee != null) {
            int rank = localName.charAt(1) - '0';

            // If the current section has no heading,
            // let the element being entered be the heading for the current
            // section.
            if (currentSection != null && !currentSection.hasHeading()) {
                // Because we do the following even if the section only has
                // an _implied_ heading, it can cause some non-intuitive
                // outlines. But the spec very intentionally requires it.
                // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=20068#c4
                currentSection.setHeadingRank(rank);
            }
            // Otherwise, if the element being entered has a rank equal to
            // or higher than the heading of the last section of the
            // outline of the current outlinee, or if the heading of the
            // last section of the outline of the current outlinee is an
            // implied heading,
            else if (rank <= currentOutlinee.getLastSectionHeadingRank()) {
                // then create a new section and append it to the outline
                // of the current outlinee element, so that this new
                // section is the new last section of that outline.
                // Let current section be that new section.
                currentSection = new Section(localName);
                currentOutlinee.getOutline().add(currentSection);

                // Let the element being entered be the new heading for the
                // current section.
                currentSection.setHeadingRank(rank);
            }
            // Otherwise, run these substeps:
            else {
                // Let candidate section be current section.
                Section candidateSection = currentSection;

                // Heading loop:
                while (candidateSection != null) {
                    // If the element being entered has a rank lower than the
                    // rank of the heading of the candidate section,
                    if (rank > candidateSection.getHeadingRank()) {
                        // then create a new section, and append it to candidate
                        // section.
                        // (This does not change which section is the last
                        // section in the outline.)
                        // Let current section be this new section.
                        currentSection = new Section(localName);
                        currentSection.setParent(candidateSection);
                        candidateSection.getSections().add(currentSection);

                        // Let the element being entered be the new heading for
                        // the current section.
                        currentSection.setHeadingRank(rank);

                        // Abort these substeps.
                        break;
                    }

                    // Let new candidate section be the section that contains
                    // candidate section in the outline of current outlinee.
                    // Let candidate section be new candidate section.
                    candidateSection = candidateSection.getParent();

                    // Return to the step labeled heading loop.
                }
            }

            // Push the element being entered onto the stack.
            // (This causes the algorithm to skip any descendants of the
            // element.)
            outlineStack.push(new Element(currentWalkDepth, localName, hidden));
            inHeadingContentOrHiddenElement = true;
            currentSection.setHeadingElementName(localName);
        }
        contentHandler.startElement(uri, localName, qName, atts);
    }
@see org.xml.sax.helpers.XMLFilterImpl#startElement(java.lang.String,
     java.lang.String, java.lang.String, org.xml.sax.Attributes)
src/nu/validator/servlet/OutlineBuildingXMLReaderWrapper.java