@Override
  public void handleControlStart(
      String uri,
      String localname,
      String qName,
      Attributes attributes,
      String staticId,
      final String effectiveId,
      XFormsSingleNodeControl xformsControl)
      throws SAXException {

    final String groupElementName = getContainingElementName();
    final String xhtmlPrefix = handlerContext.findXHTMLPrefix();
    final String groupElementQName = XMLUtils.buildQName(xhtmlPrefix, groupElementName);

    final ElementHandlerController controller = handlerContext.getController();

    // Get classes
    // TODO: should use getContainerAttributes() instead?
    final StringBuilder classes = getInitialClasses(uri, localname, attributes, null);
    handleMIPClasses(classes, getPrefixedId(), xformsControl);

    final ContentHandler contentHandler = controller.getOutput();

    // Start xhtml:fieldset element if needed
    if (!handlerContext.isNewXHTMLLayout())
      contentHandler.startElement(
          XMLConstants.XHTML_NAMESPACE_URI,
          groupElementName,
          groupElementQName,
          getAttributes(attributes, classes.toString(), effectiveId));

    // Output an xhtml:legend element if and only if there is an xforms:label element. This help
    // with
    // styling in particular.
    final boolean hasLabel = XFormsControl.hasLabel(containingDocument, getPrefixedId());
    if (hasLabel) {

      // Handle label classes
      reusableAttributes.clear();
      reusableAttributes.addAttribute(
          "", "class", "class", ContentHandlerHelper.CDATA, getLabelClasses(xformsControl));
      // The id should never be need on <legend>
      //            reusableAttributes.addAttribute("", "id", "id", ContentHandlerHelper.CDATA,
      // getLHHACId(effectiveId, LHHAC_CODES.get(LHHAC.LABEL)));

      // Output xhtml:legend with label content
      final String legendQName = XMLUtils.buildQName(xhtmlPrefix, "legend");
      contentHandler.startElement(
          XMLConstants.XHTML_NAMESPACE_URI, "legend", legendQName, reusableAttributes);
      {
        final String labelValue = getLabelValue(xformsControl);
        if (StringUtils.isNotEmpty(labelValue))
          contentHandler.characters(labelValue.toCharArray(), 0, labelValue.length());
      }
      contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "legend", legendQName);
    }
  }
  @Override
  public void handleControlEnd(
      String uri,
      String localname,
      String qName,
      Attributes attributes,
      String staticId,
      String effectiveId,
      XFormsSingleNodeControl xformsControl)
      throws SAXException {

    final ElementHandlerController controller = handlerContext.getController();

    // Close xhtml:span
    final String xhtmlPrefix = handlerContext.findXHTMLPrefix();
    final String groupElementName = getContainingElementName();
    final String groupElementQName = XMLUtils.buildQName(xhtmlPrefix, groupElementName);

    if (!handlerContext.isNewXHTMLLayout())
      controller
          .getOutput()
          .endElement(XMLConstants.XHTML_NAMESPACE_URI, groupElementName, groupElementQName);
  }
  public void handleControlStart(
      String uri,
      String localname,
      String qName,
      Attributes attributes,
      final String effectiveId,
      XFormsControl control)
      throws SAXException {

    final String groupElementName = getContainingElementName();
    final String xhtmlPrefix = handlerContext.findXHTMLPrefix();
    final String groupElementQName = XMLUtils.buildQName(xhtmlPrefix, groupElementName);

    final ElementHandlerController controller = handlerContext.getController();

    // Place interceptor on output

    // NOTE: Strictly, we should be able to do without the interceptor. We use it here because it
    // automatically handles ids and element names
    currentSavedOutput = controller.getOutput();
    if (!handlerContext.isNoScript()) {

      final boolean isMustGenerateBeginEndDelimiters =
          !handlerContext.isFullUpdateTopLevelControl(effectiveId);

      // Classes on top-level elements and characters and on the first delimiter
      final String elementClasses;
      {
        final StringBuilder classes = new StringBuilder();
        appendControlUserClasses(attributes, control, classes);
        // NOTE: Could also use getInitialClasses(uri, localname, attributes, control), but then we
        // get the
        // xforms-group-appearance-xxforms-separator class. Is that desirable?
        handleMIPClasses(
            classes,
            getPrefixedId(),
            control); // as of August 2009, actually only need the marker class as well as
                      // xforms-disabled if the group is non-relevant
        elementClasses = classes.toString();
      }

      outputInterceptor =
          new OutputInterceptor(
              currentSavedOutput,
              groupElementQName,
              new OutputInterceptor.Listener() {

                // Classes on first delimiter
                private final String firstDelimiterClasses;

                {
                  final StringBuilder classes = new StringBuilder("xforms-group-begin-end");
                  if (elementClasses.length() > 0) {
                    classes.append(' ');
                    classes.append(elementClasses);
                  }
                  firstDelimiterClasses = classes.toString();
                }

                public void generateFirstDelimiter(OutputInterceptor outputInterceptor)
                    throws SAXException {
                  // Delimiter: begin group
                  if (isMustGenerateBeginEndDelimiters) {
                    outputInterceptor.outputDelimiter(
                        currentSavedOutput,
                        outputInterceptor.getDelimiterNamespaceURI(),
                        outputInterceptor.getDelimiterPrefix(),
                        outputInterceptor.getDelimiterLocalName(),
                        firstDelimiterClasses,
                        "group-begin-" + XFormsUtils.namespaceId(containingDocument, effectiveId));
                  }
                }
              });

      controller.setOutput(new DeferredXMLReceiverImpl(outputInterceptor));

      // Set control classes
      outputInterceptor.setAddedClasses(elementClasses);
    } else if (isNonRelevant(control)) {
      // In noscript, if the group not visible, set output to a black hole
      controller.setOutput(new DeferredXMLReceiverAdapter());
    }

    // Don't support label, help, alert, or hint and other appearances, only the content!
  }
  @Override
  protected void handleControlStart(
      String uri,
      String localname,
      String qName,
      Attributes attributes,
      String staticId,
      final String effectiveId,
      XFormsControl control)
      throws SAXException {

    final String xhtmlPrefix = handlerContext.findXHTMLPrefix();
    final String spanQName = XMLUtils.buildQName(xhtmlPrefix, "span");

    // Determine whether this case is visible
    final XFormsCaseControl caseControl =
        (XFormsCaseControl) containingDocument.getControls().getObjectByEffectiveId(effectiveId);
    if (!handlerContext.isTemplate() && caseControl != null) {
      // This case is visible if it is selected or if the switch is read-only and we display
      // read-only as static
      isVisible = caseControl.isVisible();
    } else {
      isVisible = false;
    }

    final ElementHandlerController controller = handlerContext.getController();
    currentSavedOutput = controller.getOutput();

    // Place interceptor if needed
    if (!handlerContext.isNoScript()) {

      final boolean isMustGenerateBeginEndDelimiters =
          !handlerContext.isFullUpdateTopLevelControl(effectiveId);

      // Classes on top-level elements and characters and on the first delimiter
      final String elementClasses;
      {
        final StringBuilder classes = new StringBuilder();
        appendControlUserClasses(attributes, control, classes);
        // Don't add MIP classes as they can conflict with classes of nested content if used outside
        // <tr>, etc.
        elementClasses = classes.toString();
      }

      currentOutputInterceptor =
          new OutputInterceptor(
              currentSavedOutput,
              spanQName,
              new OutputInterceptor.Listener() {

                // Classes on first delimiter
                private final String firstDelimiterClasses;

                {
                  final StringBuilder classes = new StringBuilder("xforms-case-begin-end");
                  if (elementClasses.length() > 0) {
                    classes.append(' ');
                    classes.append(elementClasses);
                  }
                  firstDelimiterClasses = classes.toString();
                }

                public void generateFirstDelimiter(OutputInterceptor outputInterceptor)
                    throws SAXException {
                  if (isMustGenerateBeginEndDelimiters) {
                    // Delimiter: begin case
                    outputInterceptor.outputDelimiter(
                        currentSavedOutput,
                        outputInterceptor.getDelimiterNamespaceURI(),
                        outputInterceptor.getDelimiterPrefix(),
                        outputInterceptor.getDelimiterLocalName(),
                        firstDelimiterClasses,
                        "xforms-case-begin-"
                            + XFormsUtils.namespaceId(containingDocument, effectiveId));
                  }
                }
              });

      final String controlClasses;
      {
        final StringBuilder classes =
            new StringBuilder(isVisible ? "xforms-case-selected" : "xforms-case-deselected");
        if (elementClasses.length() > 0) {
          classes.append(' ');
          classes.append(elementClasses);
        }
        controlClasses = classes.toString();
      }

      currentOutputInterceptor.setAddedClasses(controlClasses);

      controller.setOutput(new DeferredXMLReceiverImpl(currentOutputInterceptor));
    } else if (!isVisible) {
      // Case not visible, set output to a black hole
      controller.setOutput(new DeferredXMLReceiverAdapter());
    }

    handlerContext.pushCaseContext(isVisible);
  }