private void handleItemCompact(
      ContentHandler contentHandler,
      String optionQName,
      XFormsValueControl xformsControl,
      boolean isMultiple,
      Item item)
      throws SAXException {

    final String itemClasses = getItemClasses(item, null);
    final AttributesImpl optionAttributes =
        getAttributes(XMLUtils.EMPTY_ATTRIBUTES, itemClasses, null);
    // Add item attributes to option
    addItemAttributes(item, optionAttributes);
    optionAttributes.addAttribute(
        "", "value", "value", ContentHandlerHelper.CDATA, item.getExternalValue(pipelineContext));

    // Figure out whether what items are selected
    boolean isSelected =
        isSelected(pipelineContext, handlerContext, xformsControl, isMultiple, item);
    if (isSelected)
      optionAttributes.addAttribute(
          "", "selected", "selected", ContentHandlerHelper.CDATA, "selected");

    // xhtml:option
    contentHandler.startElement(
        XMLConstants.XHTML_NAMESPACE_URI, "option", optionQName, optionAttributes);
    final String label = item.getLabel();
    if (label != null) contentHandler.characters(label.toCharArray(), 0, label.length());
    contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "option", optionQName);
  }
 private static boolean isSelected(
     PipelineContext pipelineContext,
     HandlerContext handlerContext,
     XFormsValueControl xformsControl,
     boolean isMultiple,
     Item item) {
   boolean isSelected;
   if (!handlerContext.isTemplate() && xformsControl != null) {
     final String itemValue = ((item.getValue() == null) ? "" : item.getValue()).trim();
     final String controlValue =
         ((xformsControl.getValue(pipelineContext) == null)
                 ? ""
                 : xformsControl.getValue(pipelineContext))
             .trim();
     isSelected = XFormsItemUtils.isSelected(isMultiple, controlValue, itemValue);
   } else {
     isSelected = false;
   }
   return isSelected;
 }
 private static void addItemAttributes(Item item, AttributesImpl spanAttributes) {
   final Map<String, String> itemAttributes = item.getAttributes();
   if (itemAttributes != null && itemAttributes.size() > 0) {
     for (final Map.Entry<String, String> entry : itemAttributes.entrySet()) {
       final String attributeName = entry.getKey();
       if (!attributeName.equals("class")) { // class is handled separately
         spanAttributes.addAttribute(
             "", attributeName, attributeName, ContentHandlerHelper.CDATA, entry.getValue());
       }
     }
   }
 }
 private static String getItemClasses(Item item, String initialClasses) {
   final Map<String, String> itemAttributes = item.getAttributes();
   final StringBuilder sb =
       (initialClasses != null) ? new StringBuilder(initialClasses) : new StringBuilder();
   if (itemAttributes != null) {
     final String itemClassValue = itemAttributes.get("class");
     if (itemClassValue != null) {
       if (sb.length() > 0) sb.append(' ');
       sb.append(itemClassValue);
     }
   }
   return sb.toString();
 }
  @Override
  public void storeExternalValue(PropertyContext propertyContext, String value, String type) {

    if (!(this
        instanceof
        XFormsSelectControl)) { // kind of a HACK due to the way our class hierarchy is setup
      // Handle xforms:select1-specific logic

      // Decrypt incoming value. With open selection, values are sent to the client.
      if (isEncryptItemValues()) {
        try {
          value = XFormsItemUtils.decryptValue(propertyContext, value);
        } catch (IllegalArgumentException e) {
          getIndentedLogger()
              .logError(
                  "", "exception decrypting value", "control id", getEffectiveId(), "value", value);
          throw e;
        }
      }

      // Current control value
      final String controlValue = getValue(propertyContext);

      // Iterate over all the items
      final Itemset itemset = getItemset(propertyContext);
      final List<XFormsEvent> selectEvents = new ArrayList<XFormsEvent>();
      final List<XFormsEvent> deselectEvents = new ArrayList<XFormsEvent>();
      if (itemset != null) {
        for (Item currentItem : itemset.toList()) {
          final String currentItemValue = currentItem.getValue();
          final boolean itemWasSelected = controlValue.equals(currentItemValue);
          final boolean itemIsSelected;
          if (value.equals(currentItemValue)) {
            // Value is currently selected in the UI
            itemIsSelected = true;
          } else {
            // Value is currently NOT selected in the UI
            itemIsSelected = false;
          }

          // Handle xforms-select / xforms-deselect
          // TODO: Dispatch to itemset or item once we support doing that
          if (!itemWasSelected && itemIsSelected) {
            selectEvents.add(new XFormsSelectEvent(containingDocument, this, currentItemValue));
          } else if (itemWasSelected && !itemIsSelected) {
            deselectEvents.add(new XFormsDeselectEvent(containingDocument, this, currentItemValue));
          }
        }
      }

      // Dispatch xforms-deselect events
      if (deselectEvents.size() > 0) {
        for (XFormsEvent currentEvent : deselectEvents) {
          currentEvent
              .getTargetObject()
              .getXBLContainer(containingDocument)
              .dispatchEvent(propertyContext, currentEvent);
        }
      }
      // Select events must be sent after all xforms-deselect events
      final boolean hasSelectedItem = selectEvents.size() > 0;
      if (hasSelectedItem) {
        for (XFormsEvent currentEvent : selectEvents) {
          currentEvent
              .getTargetObject()
              .getXBLContainer(containingDocument)
              .dispatchEvent(propertyContext, currentEvent);
        }
      }

      if (hasSelectedItem || isOpenSelection()) {
        // Only then do we store the external value. This ensures that if the value is NOT in the
        // itemset AND
        // we are a closed selection then we do NOT store the value in instance.
        super.storeExternalValue(propertyContext, value, type);
      }
    } else {
      // Forward to superclass
      super.storeExternalValue(propertyContext, value, type);
    }
  }
  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);
  }
  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);
    }
  }