@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);
    }
  }
  protected void handleControlStart(
      String uri,
      String localname,
      String qName,
      Attributes attributes,
      String effectiveId,
      XFormsControl control)
      throws SAXException {

    final XFormsTriggerControl triggerControl = (XFormsTriggerControl) control;
    final XMLReceiver xmlReceiver = handlerContext.getController().getOutput();

    final AttributesImpl containerAttributes =
        getEmptyNestedControlAttributesMaybeWithId(
            uri, localname, attributes, effectiveId, triggerControl, true);

    // TODO: needs f:url-norewrite="true"?
    containerAttributes.addAttribute("", "href", "href", XMLReceiverHelper.CDATA, "#");

    // xhtml:a
    final String xhtmlPrefix = handlerContext.findXHTMLPrefix();
    final String aQName = XMLUtils.buildQName(xhtmlPrefix, ENCLOSING_ELEMENT_NAME);
    xmlReceiver.startElement(
        XMLConstants.XHTML_NAMESPACE_URI, ENCLOSING_ELEMENT_NAME, aQName, containerAttributes);
    {
      final String labelValue = getTriggerLabel(triggerControl);
      final boolean mustOutputHTMLFragment = triggerControl != null && triggerControl.isHTMLLabel();
      outputLabelText(xmlReceiver, triggerControl, labelValue, xhtmlPrefix, mustOutputHTMLFragment);
    }
    xmlReceiver.endElement(XMLConstants.XHTML_NAMESPACE_URI, ENCLOSING_ELEMENT_NAME, aQName);
  }
  public void outputDelimiter(ContentHandler contentHandler, String classes, String id)
      throws SAXException {

    reusableAttributes.clear();
    if (id != null) reusableAttributes.addAttribute("", "id", "id", ContentHandlerHelper.CDATA, id);

    if (classes != null)
      reusableAttributes.addAttribute("", "class", "class", ContentHandlerHelper.CDATA, classes);

    final String delimiterQName = XMLUtils.buildQName(delimiterPrefix, delimiterLocalName);
    contentHandler.startElement(
        delimiterNamespaceURI, delimiterLocalName, delimiterQName, reusableAttributes);
    contentHandler.endElement(delimiterNamespaceURI, delimiterLocalName, delimiterQName);
  }
Example #4
0
  public static AttributesImpl addOrReplaceAttribute(
      Attributes attributes, String uri, String prefix, String localname, String value) {
    final AttributesImpl newAttributes = new AttributesImpl();
    boolean replaced = false;
    for (int i = 0; i < attributes.getLength(); i++) {
      final String attributeURI = attributes.getURI(i);
      final String attributeValue = attributes.getValue(i);
      final String attributeType = attributes.getType(i);
      final String attributeQName = attributes.getQName(i);
      final String attributeLocalname = attributes.getLocalName(i);

      if (uri.equals(attributeURI) && localname.equals(attributeLocalname)) {
        // Found existing attribute
        replaced = true;
        newAttributes.addAttribute(
            uri,
            localname,
            XMLUtils.buildQName(prefix, localname),
            ContentHandlerHelper.CDATA,
            value);
      } else {
        // Not a matched attribute
        newAttributes.addAttribute(
            attributeURI, attributeLocalname, attributeQName, attributeType, attributeValue);
      }
    }
    if (!replaced) {
      // Attribute did not exist already so add it
      newAttributes.addAttribute(
          uri,
          localname,
          XMLUtils.buildQName(prefix, localname),
          ContentHandlerHelper.CDATA,
          value);
    }
    return newAttributes;
  }
  @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!
  }
  public static void handleItemFull(
      PipelineContext pipelineContext,
      HandlerContext handlerContext,
      ContentHandler contentHandler,
      AttributesImpl reusableAttributes,
      Attributes attributes,
      String xhtmlPrefix,
      String spanQName,
      XFormsContainingDocument containingDocument,
      XFormsValueControl xformsControl,
      String effectiveId,
      String itemEffectiveId,
      boolean isMultiple,
      String type,
      Item item,
      boolean isFirst)
      throws SAXException {

    // Whether this is selected
    boolean isSelected =
        isSelected(pipelineContext, handlerContext, xformsControl, isMultiple, item);

    // xhtml:span enclosing input and label
    final String itemClasses =
        getItemClasses(item, isSelected ? "xforms-selected" : "xforms-deselected");
    final AttributesImpl spanAttributes =
        getAttributes(reusableAttributes, XMLUtils.EMPTY_ATTRIBUTES, itemClasses, null);
    // Add item attributes to span
    addItemAttributes(item, spanAttributes);
    contentHandler.startElement(
        XMLConstants.XHTML_NAMESPACE_URI, "span", spanQName, spanAttributes);

    {
      {
        // xhtml:input
        final String inputQName = XMLUtils.buildQName(xhtmlPrefix, "input");

        reusableAttributes.clear();
        reusableAttributes.addAttribute(
            "", "id", "id", ContentHandlerHelper.CDATA, itemEffectiveId);
        reusableAttributes.addAttribute("", "type", "type", ContentHandlerHelper.CDATA, type);

        // Get group name from selection control if possible, otherwise use effective id
        final String name =
            (!isMultiple && xformsControl instanceof XFormsSelect1Control)
                ? ((XFormsSelect1Control) xformsControl).getGroupName()
                : effectiveId;
        reusableAttributes.addAttribute("", "name", "name", ContentHandlerHelper.CDATA, name);

        reusableAttributes.addAttribute(
            "",
            "value",
            "value",
            ContentHandlerHelper.CDATA,
            item.getExternalValue(pipelineContext));

        if (!handlerContext.isTemplate() && xformsControl != null) {

          if (isSelected) {
            reusableAttributes.addAttribute(
                "", "checked", "checked", ContentHandlerHelper.CDATA, "checked");
          }

          if (isFirst) {
            // Handle accessibility attributes
            handleAccessibilityAttributes(attributes, reusableAttributes);
          }
        }

        handleDisabledAttribute(reusableAttributes, containingDocument, xformsControl);
        contentHandler.startElement(
            XMLConstants.XHTML_NAMESPACE_URI, "input", inputQName, reusableAttributes);
        contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "input", inputQName);
      }

      // We don't output the label within <input></input>, because XHTML won't display it.
      final String label = item.getLabel();
      if (label != null) { // allow null label to tell not to output the <label> element at all
        reusableAttributes.clear();
        outputLabelFor(
            handlerContext,
            reusableAttributes,
            itemEffectiveId,
            itemEffectiveId,
            LHHAC.LABEL,
            "label",
            label,
            false,
            false); // TODO: may be HTML for full appearance
      }
    }

    contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "span", spanQName);
  }
  private void outputFull(
      String uri,
      String localname,
      Attributes attributes,
      String effectiveId,
      XFormsValueControl xformsControl,
      Itemset itemset,
      boolean isMultiple,
      boolean isBooleanInput)
      throws SAXException {
    final ContentHandler contentHandler = handlerContext.getController().getOutput();
    final AttributesImpl containerAttributes =
        getContainerAttributes(uri, localname, attributes, effectiveId, xformsControl, !isFull);
    final String xhtmlPrefix = handlerContext.findXHTMLPrefix();

    final String fullItemType = isMultiple ? "checkbox" : "radio";

    // In noscript mode, use <fieldset>

    // TODO: This really hasn't much to do with noscript; should we always use fieldset, or make
    // this an
    // option? Benefit of limiting to noscript is that then no JS change is needed
    final String containingElementName = handlerContext.isNoScript() ? "fieldset" : "span";
    final String containingElementQName = XMLUtils.buildQName(xhtmlPrefix, containingElementName);

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

      // Old layout always output container <span>/<fieldset>, and in new layout we only put it for
      // select/select1
      final boolean outputContainerElement = !isBooleanInput || !handlerContext.isSpanHTMLLayout();
      if (outputContainerElement)
        contentHandler.startElement(
            XMLConstants.XHTML_NAMESPACE_URI,
            containingElementName,
            containingElementQName,
            containerAttributes);
      {
        if (handlerContext.isNoScript()) {
          // Output <legend>
          final String legendName = "legend";
          final String legendQName = XMLUtils.buildQName(xhtmlPrefix, legendName);
          reusableAttributes.clear();
          // TODO: handle other attributes? xforms-disabled?
          reusableAttributes.addAttribute(
              "", "class", "class", ContentHandlerHelper.CDATA, "xforms-label");
          contentHandler.startElement(
              XMLConstants.XHTML_NAMESPACE_URI, legendName, legendQName, reusableAttributes);
          if (xformsControl != null) {
            final boolean mustOutputHTMLFragment = xformsControl.isHTMLLabel(pipelineContext);
            outputLabelText(
                contentHandler,
                xformsControl,
                xformsControl.getLabel(pipelineContext),
                xhtmlPrefix,
                mustOutputHTMLFragment);
          }
          contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, legendName, legendQName);
        }

        if (itemset != null) {
          int itemIndex = 0;
          for (Iterator<Item> i = itemset.toList().iterator(); i.hasNext(); itemIndex++) {
            final Item item = i.next();
            final String itemEffectiveId = getItemId(effectiveId, Integer.toString(itemIndex));
            handleItemFull(
                pipelineContext,
                handlerContext,
                contentHandler,
                reusableAttributes,
                attributes,
                xhtmlPrefix,
                spanQName,
                containingDocument,
                xformsControl,
                effectiveId,
                itemEffectiveId,
                isMultiple,
                fullItemType,
                item,
                itemIndex == 0);
          }
        }
      }
      if (outputContainerElement)
        contentHandler.endElement(
            XMLConstants.XHTML_NAMESPACE_URI, containingElementName, containingElementQName);
    }

    // NOTE: Templates for full items are output globally in XHTMLBodyHandler
  }
  public void outputContent(
      String uri,
      String localname,
      Attributes attributes,
      String effectiveId,
      final XFormsValueControl xformsSelect1Control,
      Itemset itemset,
      final boolean isMultiple,
      final boolean isFull,
      boolean isBooleanInput)
      throws SAXException {

    final ContentHandler contentHandler = handlerContext.getController().getOutput();

    final AttributesImpl containerAttributes =
        getContainerAttributes(
            uri, localname, attributes, effectiveId, xformsSelect1Control, !isFull);

    final String xhtmlPrefix = handlerContext.findXHTMLPrefix();
    if (!isStaticReadonly(xformsSelect1Control)) {
      if (isFull) {
        // Full appearance
        outputFull(
            uri,
            localname,
            attributes,
            effectiveId,
            xformsSelect1Control,
            itemset,
            isMultiple,
            isBooleanInput);
      } else {

        if (isOpenSelection) {

          if (isAutocomplete) {

            // Create xhtml:span
            final String spanQName = XMLUtils.buildQName(xhtmlPrefix, "span");
            contentHandler.startElement(
                XMLConstants.XHTML_NAMESPACE_URI, "span", spanQName, containerAttributes);

            {
              {
                // Create xhtml:input
                final String inputQName = XMLUtils.buildQName(xhtmlPrefix, "input");

                reusableAttributes.clear();
                reusableAttributes.addAttribute(
                    "", "type", "type", ContentHandlerHelper.CDATA, "text");
                reusableAttributes.addAttribute(
                    "",
                    "name",
                    "name",
                    ContentHandlerHelper.CDATA,
                    "xforms-select1-open-input-" + effectiveId);
                reusableAttributes.addAttribute(
                    "", "class", "class", ContentHandlerHelper.CDATA, "xforms-select1-open-input");
                reusableAttributes.addAttribute(
                    "", "autocomplete", "autocomplete", ContentHandlerHelper.CDATA, "off");

                final String value =
                    (xformsSelect1Control == null)
                        ? null
                        : xformsSelect1Control.getValue(pipelineContext);
                // NOTE: With open selection, we send all values to the client but not encrypt them
                // because the client matches on values
                reusableAttributes.addAttribute(
                    "", "value", "value", ContentHandlerHelper.CDATA, (value == null) ? "" : value);
                handleDisabledAttribute(
                    reusableAttributes, containingDocument, xformsSelect1Control);
                contentHandler.startElement(
                    XMLConstants.XHTML_NAMESPACE_URI, "input", inputQName, reusableAttributes);

                contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "input", inputQName);
              }
              {
                // Create xhtml:select
                final String selectQName = XMLUtils.buildQName(xhtmlPrefix, "select");

                reusableAttributes.clear();
                reusableAttributes.addAttribute(
                    "", "class", "class", ContentHandlerHelper.CDATA, "xforms-select1-open-select");

                if (isCompact)
                  reusableAttributes.addAttribute(
                      "", "multiple", "multiple", ContentHandlerHelper.CDATA, "multiple");

                // Handle accessibility attributes
                handleAccessibilityAttributes(attributes, reusableAttributes);

                contentHandler.startElement(
                    XMLConstants.XHTML_NAMESPACE_URI, "select", selectQName, reusableAttributes);

                final String optionQName = XMLUtils.buildQName(xhtmlPrefix, "option");
                handleItemCompact(
                    contentHandler,
                    optionQName,
                    xformsSelect1Control,
                    isMultiple,
                    EMPTY_TOP_LEVEL_ITEM);
                if (itemset != null) {
                  for (final Item item : itemset.toList()) {
                    if (item.getValue() != null)
                      handleItemCompact(
                          contentHandler, optionQName, xformsSelect1Control, isMultiple, item);
                  }
                }

                contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "select", selectQName);
              }
            }

            contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "span", spanQName);
          } else {
            // We do not support other appearances or regular open selection for now
            throw new ValidationException(
                "Open selection currently only supports the xxforms:autocomplete appearance.",
                new ExtendedLocationData(
                    handlerContext.getLocationData(),
                    "producing markup for xforms:" + localname + " control",
                    (xformsSelect1Control != null)
                        ? xformsSelect1Control.getControlElement()
                        : null));
          }

        } else if (isTree) {
          // xxforms:tree appearance

          // Create xhtml:div with tree info
          final String divQName = XMLUtils.buildQName(xhtmlPrefix, "div");

          handleDisabledAttribute(containerAttributes, containingDocument, xformsSelect1Control);
          contentHandler.startElement(
              XMLConstants.XHTML_NAMESPACE_URI, "div", divQName, containerAttributes);
          if (itemset != null) { // can be null if the control is non-relevant
            outputJSONTreeInfo(xformsSelect1Control, itemset, isMultiple, contentHandler);
          }
          contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "div", divQName);

        } else if (isMenu) {
          // xxforms:menu appearance

          // Create enclosing xhtml:div
          final String divQName = XMLUtils.buildQName(xhtmlPrefix, "div");
          final String ulQName = XMLUtils.buildQName(xhtmlPrefix, "ul");
          final String liQName = XMLUtils.buildQName(xhtmlPrefix, "li");
          final String aQName = XMLUtils.buildQName(xhtmlPrefix, "a");

          handleDisabledAttribute(containerAttributes, containingDocument, xformsSelect1Control);
          contentHandler.startElement(
              XMLConstants.XHTML_NAMESPACE_URI, "div", divQName, containerAttributes);
          if (itemset != null) { // can be null if the control is non-relevant
            // Create xhtml:div with initial menu entries
            {
              itemset.visit(
                  contentHandler,
                  new ItemsetListener() {

                    private boolean groupJustStarted = false;

                    public void startLevel(ContentHandler contentHandler, Item item)
                        throws SAXException {

                      final boolean isTopLevel = item == null;

                      reusableAttributes.clear();
                      final String divClasses = isTopLevel ? "yuimenubar" : "yuimenu";
                      reusableAttributes.addAttribute(
                          "", "class", "class", ContentHandlerHelper.CDATA, divClasses);
                      contentHandler.startElement(
                          XMLConstants.XHTML_NAMESPACE_URI, "div", divQName, reusableAttributes);

                      reusableAttributes.clear();
                      reusableAttributes.addAttribute(
                          "", "class", "class", ContentHandlerHelper.CDATA, "bd");
                      contentHandler.startElement(
                          XMLConstants.XHTML_NAMESPACE_URI, "div", divQName, reusableAttributes);

                      reusableAttributes.clear();
                      // NOTE: We just decide to put item classes on <ul>
                      final String classes =
                          isTopLevel ? "first-of-type" : getItemClasses(item, "first-of-type");
                      reusableAttributes.addAttribute(
                          "", "class", "class", ContentHandlerHelper.CDATA, classes);
                      contentHandler.startElement(
                          XMLConstants.XHTML_NAMESPACE_URI, "ul", ulQName, reusableAttributes);

                      groupJustStarted = true;
                    }

                    public void endLevel(ContentHandler contentHandler) throws SAXException {
                      contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "ul", ulQName);
                      contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "div", divQName);
                      contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "div", divQName);

                      groupJustStarted = false;
                    }

                    public void startItem(ContentHandler contentHandler, Item item, boolean first)
                        throws SAXException {

                      final String liClasses;
                      {
                        final StringBuilder sb =
                            new StringBuilder(item.isTopLevel() ? "yuimenubaritem" : "yuimenuitem");
                        if (groupJustStarted) sb.append(" first-of-type");
                        liClasses = getItemClasses(item, sb.toString());
                      }
                      reusableAttributes.clear();
                      reusableAttributes.addAttribute(
                          "", "class", "class", ContentHandlerHelper.CDATA, liClasses);
                      contentHandler.startElement(
                          XMLConstants.XHTML_NAMESPACE_URI, "li", liQName, reusableAttributes);

                      reusableAttributes.clear();
                      reusableAttributes.addAttribute(
                          "", "href", "href", ContentHandlerHelper.CDATA, "#");
                      contentHandler.startElement(
                          XMLConstants.XHTML_NAMESPACE_URI, "a", aQName, reusableAttributes);

                      final String text = item.getLabel();
                      contentHandler.characters(text.toCharArray(), 0, text.length());

                      contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "a", aQName);

                      groupJustStarted = false;
                    }

                    public void endItem(ContentHandler contentHandler) throws SAXException {
                      contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "li", liQName);

                      groupJustStarted = false;
                    }
                  });
            }

            // Create xhtml:div with tree info
            reusableAttributes.clear();
            reusableAttributes.addAttribute(
                "", "class", "class", ContentHandlerHelper.CDATA, "xforms-initially-hidden");

            contentHandler.startElement(
                XMLConstants.XHTML_NAMESPACE_URI, "div", divQName, reusableAttributes);
            if (itemset != null) { // can be null if the control is non-relevant
              outputJSONTreeInfo(xformsSelect1Control, itemset, isMultiple, contentHandler);
            }
            contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "div", divQName);
          }
          contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "div", divQName);

        } else {
          // Create xhtml:select
          final String selectQName = XMLUtils.buildQName(xhtmlPrefix, "select");
          containerAttributes.addAttribute(
              "",
              "name",
              "name",
              ContentHandlerHelper.CDATA,
              effectiveId); // necessary for noscript mode

          if (isCompact)
            containerAttributes.addAttribute(
                "", "multiple", "multiple", ContentHandlerHelper.CDATA, "multiple");

          // Handle accessibility attributes
          handleAccessibilityAttributes(attributes, containerAttributes);

          handleDisabledAttribute(containerAttributes, containingDocument, xformsSelect1Control);
          contentHandler.startElement(
              XMLConstants.XHTML_NAMESPACE_URI, "select", selectQName, containerAttributes);
          {
            final String optionQName = XMLUtils.buildQName(xhtmlPrefix, "option");
            final String optGroupQName = XMLUtils.buildQName(xhtmlPrefix, "optgroup");

            if (itemset != null) {

              // Work in progress for in-bounds/out-of-bounds
              //                        if (!((XFormsSelect1Control)
              // xformsControl).isInBounds(items)) {
              //                            // Control is out of bounds so add first item with out
              // of bound value to handle this
              //                            handleItemCompact(contentHandler, optionQName,
              // xformsControl, isMultiple,
              //                                    new
              // XFormsItemUtils.Item(XFormsProperties.isEncryptItemValues(containingDocument),
              //                                            Collections.EMPTY_LIST, "",
              // xformsControl.getValue(pipelineContext), 1));
              //                        }

              itemset.visit(
                  contentHandler,
                  new ItemsetListener() {

                    private int optgroupCount = 0;

                    public void startLevel(ContentHandler contentHandler, Item item)
                        throws SAXException {}

                    public void endLevel(ContentHandler contentHandler) throws SAXException {
                      if (optgroupCount-- > 0) {
                        // End xhtml:optgroup
                        contentHandler.endElement(
                            XMLConstants.XHTML_NAMESPACE_URI, "optgroup", optGroupQName);
                      }
                    }

                    public void startItem(ContentHandler contentHandler, Item item, boolean first)
                        throws SAXException {

                      final String label = item.getLabel();
                      final String value = item.getValue();

                      if (value == null) {
                        final String itemClasses = getItemClasses(item, null);
                        final AttributesImpl optGroupAttributes =
                            getAttributes(XMLUtils.EMPTY_ATTRIBUTES, itemClasses, null);
                        if (label != null)
                          optGroupAttributes.addAttribute(
                              "", "label", "label", ContentHandlerHelper.CDATA, label);

                        // Start xhtml:optgroup
                        contentHandler.startElement(
                            XMLConstants.XHTML_NAMESPACE_URI,
                            "optgroup",
                            optGroupQName,
                            optGroupAttributes);
                        optgroupCount++;
                      } else {
                        handleItemCompact(
                            contentHandler, optionQName, xformsSelect1Control, isMultiple, item);
                      }
                    }

                    public void endItem(ContentHandler contentHandler) throws SAXException {}
                  });
            }
          }
          contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "select", selectQName);
        }
      }
    } else {
      // Read-only mode

      final String spanQName = XMLUtils.buildQName(xhtmlPrefix, "span");
      contentHandler.startElement(
          XMLConstants.XHTML_NAMESPACE_URI, "span", spanQName, containerAttributes);
      if (!handlerContext.isTemplate()) {
        final String value =
            (xformsSelect1Control == null || xformsSelect1Control.getValue(pipelineContext) == null)
                ? ""
                : xformsSelect1Control.getValue(pipelineContext);
        final StringBuilder sb = new StringBuilder();
        if (itemset != null) {
          int selectedFound = 0;
          for (final Item currentItem : itemset.toList()) {
            if (XFormsItemUtils.isSelected(isMultiple, value, currentItem.getValue())) {
              if (selectedFound > 0) sb.append(" - ");
              sb.append(currentItem.getLabel());
              selectedFound++;
            }
          }
        }

        if (sb.length() > 0) {
          final String result = sb.toString();
          contentHandler.characters(result.toCharArray(), 0, result.length());
        }
      }
      contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "span", spanQName);
    }
  }
  @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);
  }