public void endElement(String uri, String localname, String qName) throws SAXException {

    level--;

    // Check for XForms or extension namespaces
    //        final boolean isXForms = XFormsConstants.XFORMS_NAMESPACE_URI.equals(uri);
    //        final boolean isXXForms = XFormsConstants.XXFORMS_NAMESPACE_URI.equals(uri);
    //        final boolean isEXForms = XFormsConstants.EXFORMS_NAMESPACE_URI.equals(uri);
    //        final boolean isXBL = XFormsConstants.XBL_NAMESPACE_URI.equals(uri);
    final boolean isXHTML = XMLConstants.XHTML_NAMESPACE_URI.equals(uri);
    //        final boolean isXFormsOrExtension = isXForms || isXXForms || isEXForms || isXBL;
    final boolean isXFormsOrExtension =
        !isXHTML && !"".equals(uri); // TODO: how else can we handle components?

    if (level > 0 || !ignoreRootElement) {
      // We are within preserved content or we output regular XForms content
      if (inXFormsOrExtension && (inPreserve || isXFormsOrExtension)) {
        super.endElement(uri, localname, qName);
      }

      if (inPreserve && level == preserveLevel) {
        // Leaving preserved content
        inPreserve = false;
      }

      if (inXFormsOrExtension && level == xformsLevel) {
        // Leaving model or controls
        inXFormsOrExtension = false;
        sendEndPrefixMappings();
      }
    } else {
      // Just close the root element
      super.endElement(uri, localname, qName);
      sendEndPrefixMappings();
    }

    if (!inXFormsOrExtension) {
      xmlBaseStack.pop();
    }

    namespaceSupport.endElement();
  }
  public void startElement(String uri, String localname, String qName, Attributes attributes)
      throws SAXException {

    namespaceSupport.startElement();

    // Handle location data
    if (locationData == null && locator != null && mustOutputFirstElement) {
      final String systemId = locator.getSystemId();
      if (systemId != null) {
        locationData =
            new LocationData(systemId, locator.getLineNumber(), locator.getColumnNumber());
      }
    }

    // Check for XForms or extension namespaces
    final boolean isXForms = XFormsConstants.XFORMS_NAMESPACE_URI.equals(uri);
    final boolean isXXForms = XFormsConstants.XXFORMS_NAMESPACE_URI.equals(uri);
    final boolean isEXForms = XFormsConstants.EXFORMS_NAMESPACE_URI.equals(uri);
    final boolean isXBL = XFormsConstants.XBL_NAMESPACE_URI.equals(uri);
    final boolean isXHTML = XMLConstants.XHTML_NAMESPACE_URI.equals(uri);

    // TODO: how else can we handle components?
    // NOTE: Here we have an issue identifying which elements must have content preserved. For
    // example, an element
    // to which an XBL binding is applied should be preserved, because XBL template processing take
    // place during
    // static state analysis. In XFormsDocumentAnnotatorContentHandler, we detect XBL bindings.
    // Should we do the
    // same here again? It is wasteful to do it twice. Possibly, XFDACH could pass this information
    // here since
    // it already does all the work to detect content preservation. E.g. custom attribute.

    //        final boolean isXFormsOrExtension = isXForms || isXXForms || isEXForms || isXBL;
    final boolean isXFormsOrExtension = !isXHTML && !"".equals(uri); // see NOTE above
    final boolean isExtension =
        isXFormsOrExtension && !isXForms && !isXXForms && !isEXForms && !isXBL; // see NOTE above

    // Handle xml:base
    if (!inXFormsOrExtension) {
      final String xmlBaseAttribute = attributes.getValue(XMLConstants.XML_URI, "base");
      if (xmlBaseAttribute == null) {
        xmlBaseStack.push(xmlBaseStack.peek());
      } else {
        try {
          final URI currentXMLBaseURI = xmlBaseStack.peek();
          xmlBaseStack.push(
              currentXMLBaseURI
                  .resolve(new URI(xmlBaseAttribute))
                  .normalize()); // normalize to remove "..", etc.
        } catch (URISyntaxException e) {
          throw new ValidationException(
              "Error creating URI from: '"
                  + xmlBaseStack.peek()
                  + "' and '"
                  + xmlBaseAttribute
                  + "'.",
              e,
              new LocationData(locator));
        }
      }
    }

    // Handle properties of the form @xxforms:* when outside of models or controls
    if (!inXFormsOrExtension && !isXFormsOrExtension) {
      final int attributesCount = attributes.getLength();
      for (int i = 0; i < attributesCount; i++) {
        final String attributeURI = attributes.getURI(i);
        if (XFormsConstants.XXFORMS_NAMESPACE_URI.equals(attributeURI)) {
          // Found xxforms:* attribute
          final String attributeLocalName = attributes.getLocalName(i);
          // Only take the first occurrence into account, and make sure the property is supported
          if (properties.get(attributeLocalName) == null
              && XFormsProperties.getPropertyDefinition(attributeLocalName) != null) {
            properties.put(attributeLocalName, attributes.getValue(i));
          }
        }
      }
    }

    if (level > 0 || !ignoreRootElement) {

      // Start extracting model or controls
      if (!inXFormsOrExtension && isXFormsOrExtension) {

        inXFormsOrExtension = true;
        xformsLevel = level;

        outputFirstElementIfNeeded();

        // Add xml:base on element
        attributes =
            XMLUtils.addOrReplaceAttribute(
                attributes, XMLConstants.XML_URI, "xml", "base", getCurrentBaseURI());

        sendStartPrefixMappings();
      }

      // Check for preserved content
      if (inXFormsOrExtension && !inPreserve) {
        // TODO: Just warn?
        if (isXXForms) {
          // Check that we are getting a valid xxforms:* element
          if (XFormsConstants.ALLOWED_XXFORMS_ELEMENTS.get(localname) == null
              && !XFormsActions.isActionName(XFormsConstants.XXFORMS_NAMESPACE_URI, localname))
            throw new ValidationException(
                "Invalid extension element in XForms document: " + qName,
                new LocationData(locator));
        } else if (isEXForms) {
          // Check that we are getting a valid exforms:* element
          if (XFormsConstants.ALLOWED_EXFORMS_ELEMENTS.get(localname) == null)
            throw new ValidationException(
                "Invalid eXForms element in XForms document: " + qName, new LocationData(locator));
        } else if (isXBL) {
          // Check that we are getting a valid xbl:* element
          if (XFormsConstants.ALLOWED_XBL_ELEMENTS.get(localname) == null)
            throw new ValidationException(
                "Invalid XBL element in XForms document: " + qName, new LocationData(locator));
        }

        // Preserve as is the content of labels, etc., instances, and schemas
        if ((XFormsConstants.LABEL_HINT_HELP_ALERT_ELEMENT.get(localname)
                        != null // labels, etc. may contain XHTML
                    || "instance".equals(localname))
                && isXForms // XForms instances
            || "schema".equals(localname) && XMLConstants.XSD_URI.equals(uri) // XML schemas
            || "xbl".equals(localname)
                && isXBL // preserve everything under xbl:xbl so that templates may be processed by
                         // static state
            || isExtension) {
          inPreserve = true;
          preserveLevel = level;
        }
      }

      // We are within preserved content or we output regular XForms content
      if (inXFormsOrExtension && (inPreserve || isXFormsOrExtension)) {
        super.startElement(uri, localname, qName, attributes);
      }
    } else {
      // Just open the root element
      outputFirstElementIfNeeded();
      sendStartPrefixMappings();
      super.startElement(uri, localname, qName, attributes);
    }

    level++;
  }