public void startElement(String uri, String localname, String qName, Attributes attributes)
      throws SAXException {

    level++;
    final boolean topLevelElement = level == 1;

    if (!gotElements) {
      // Override default as we just go an element

      assert topLevelElement;

      delimiterNamespaceURI = uri;
      delimiterPrefix = XMLUtils.prefixFromQName(qName);
      delimiterLocalName = XMLUtils.localNameFromQName(qName);

      gotElements = true;
    }

    flushCharacters(false, topLevelElement);
    generateFirstDelimitersIfNeeded();

    // Add or update classes on element if needed
    super.startElement(
        uri, localname, qName, topLevelElement ? getAttributesWithClass(attributes) : attributes);
  }
  private void generateTopLevelSpanWithCharacters(String characters) throws SAXException {
    // The first element received determines the type of separator
    generateFirstDelimitersIfNeeded();

    // Wrap any other text within an xhtml:span
    super.startElement(
        XMLConstants.XHTML_NAMESPACE_URI,
        "span",
        spanQName,
        getAttributesWithClass(XMLUtils.EMPTY_ATTRIBUTES));
    final char[] chars = characters.toCharArray();
    super.characters(chars, 0, chars.length);
    super.endElement(XMLConstants.XHTML_NAMESPACE_URI, "span", spanQName);
  }
  public void flushCharacters(boolean finalFlush, boolean topLevelCharacters) throws SAXException {

    final String currentString = currentCharacters.toString();

    if (topLevelCharacters && !isAroundTableOrListElement) {
      // We handle top-level characters specially and wrap them in a span so we can hide them
      generateTopLevelSpanWithCharacters(currentCharacters.toString());
    } else {
      // Just output characters as is in deeper levels, or when around at table or list element
      final char[] chars = currentString.toCharArray();
      super.characters(chars, 0, chars.length);
    }

    currentCharacters.setLength(0);

    if (finalFlush) generateFirstDelimitersIfNeeded();
  }