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);
  }
public class XXFormsIndexChangedEvent extends XFormsUIEvent {

  private static final String OLD_INDEX_ATTRIBUTE =
      XMLUtils.buildExplodedQName(XFormsConstants.XXFORMS_NAMESPACE_URI, "old-index");
  private static final String NEW_INDEX_ATTRIBUTE =
      XMLUtils.buildExplodedQName(XFormsConstants.XXFORMS_NAMESPACE_URI, "new-index");

  private int oldIndex;
  private int newIndex;

  public XXFormsIndexChangedEvent(
      XFormsContainingDocument containingDocument,
      XFormsControl targetObject,
      int oldIndex,
      int newIndex) {
    super(containingDocument, XFormsEvents.XXFORMS_INDEX_CHANGED, targetObject);

    this.oldIndex = oldIndex;
    this.newIndex = newIndex;
  }

  @Override
  public SequenceIterator getAttribute(String name) {
    if (OLD_INDEX_ATTRIBUTE.equals(name)) {
      return SingletonIterator.makeIterator(new Int64Value(oldIndex));
    } else if (NEW_INDEX_ATTRIBUTE.equals(name)) {
      return SingletonIterator.makeIterator(new Int64Value(newIndex));
    } else {
      return super.getAttribute(name);
    }
  }
}
  @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);
    }
  }
  private void checkDelimiters(String uri, String qName, boolean topLevel) throws SAXException {

    if (topLevel && delimiterNamespaceURI == null) {
      delimiterNamespaceURI = uri;
      delimiterPrefix = XMLUtils.prefixFromQName(qName);
      delimiterLocalName = XMLUtils.localNameFromQName(qName);
    }

    if (mustGenerateFirstDelimiters) {
      // Generate first delimiter
      beginDelimiterListener.generateFirstDelimiter(this);
      mustGenerateFirstDelimiters = false;
    }
  }
  public OutputInterceptor(
      XMLReceiver output,
      String spanQName,
      Listener beginDelimiterListener,
      boolean isAroundTableOrListElement) {
    super(output);
    this.spanQName = spanQName;
    this.beginDelimiterListener = beginDelimiterListener;
    this.isAroundTableOrListElement = isAroundTableOrListElement;

    // Default to <xhtml:span>
    delimiterNamespaceURI = XMLConstants.XHTML_NAMESPACE_URI;
    delimiterPrefix = XMLUtils.prefixFromQName(spanQName);
    delimiterLocalName = XMLUtils.localNameFromQName(spanQName);
  }
  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);
  }
 /**
  * Convert a String in xs:anyURI to an xs:base64Binary.
  *
  * <p>The URI has to be a URL. It is read entirely
  */
 public static String anyURIToBase64Binary(String value) {
   InputStream is = null;
   try {
     // Read from URL and convert to Base64
     is = URLFactory.createURL(value).openStream();
     final StringBuffer sb = new StringBuffer();
     XMLUtils.inputStreamToBase64Characters(
         is,
         new ContentHandlerAdapter() {
           public void characters(char ch[], int start, int length) {
             sb.append(ch, start, length);
           }
         });
     // Return Base64 String
     return sb.toString();
   } catch (IOException e) {
     throw new OXFException(e);
   } finally {
     if (is != null) {
       try {
         is.close();
       } catch (IOException e) {
         throw new OXFException(e);
       }
     }
   }
 }
  @Test
  public void formNamespaceElements() {

    final Metadata metadata = new Metadata();
    final XFormsAnnotatorContentHandler ch = new XFormsAnnotatorContentHandler(metadata);
    XMLUtils.urlToSAX(
        "oxf:/org/orbeon/oxf/xforms/processor/test-form.xml",
        ch,
        XMLUtils.ParserConfiguration.PLAIN,
        false);

    // Test that ns information is provided for those elements
    assertNotNull(metadata.getNamespaceMapping("output-in-title").mapping);
    assertNotNull(metadata.getNamespaceMapping("html").mapping);
    assertNotNull(metadata.getNamespaceMapping("main-instance").mapping);
    assertNotNull(metadata.getNamespaceMapping("dateTime-component").mapping);
    assertNotNull(metadata.getNamespaceMapping("dateTime1-control").mapping);
    assertNotNull(metadata.getNamespaceMapping("value1-control").mapping);
    assertNotNull(metadata.getNamespaceMapping("output-in-label").mapping);
    assertNotNull(metadata.getNamespaceMapping("img-in-label").mapping);
    assertNotNull(metadata.getNamespaceMapping("span").mapping);

    // Test that ns information is NOT provided for those elements (because processed as part of
    // shadow tree processing)
    assertNull(metadata.getNamespaceMapping("instance-in-xbl"));
    assertNull(metadata.getNamespaceMapping("div-in-xbl"));

    // Test that ns information is NOT provided for those elements (because in instances or schemas)
    assertNull(metadata.getNamespaceMapping("instance-root"));
    assertNull(metadata.getNamespaceMapping("instance-value"));
    assertNull(metadata.getNamespaceMapping("xbl-instance-root"));
    assertNull(metadata.getNamespaceMapping("xbl-instance-value"));
    assertNull(metadata.getNamespaceMapping("schema-element"));
  }
 /** Transform an InputStream to a TinyTree. */
 public static DocumentInfo readTinyTree(
     Configuration configuration,
     InputStream inputStream,
     String systemId,
     boolean handleXInclude,
     boolean handleLexical) {
   final TinyBuilder treeBuilder = new TinyBuilder();
   {
     final TransformerXMLReceiver identityHandler = getIdentityTransformerHandler(configuration);
     identityHandler.setResult(treeBuilder);
     final XMLReceiver xmlReceiver;
     if (handleXInclude) {
       // Insert XIncludeContentHandler
       xmlReceiver =
           new XIncludeProcessor.XIncludeXMLReceiver(
               null,
               identityHandler,
               null,
               new TransformerURIResolver(XMLUtils.ParserConfiguration.PLAIN));
     } else {
       xmlReceiver = identityHandler;
     }
     XMLUtils.inputStreamToSAX(
         inputStream, systemId, xmlReceiver, XMLUtils.ParserConfiguration.PLAIN, handleLexical);
   }
   return (DocumentInfo) treeBuilder.getCurrentRoot();
 }
 public void deserialize(
     PropertyContext propertyContext,
     ConnectionResult connectionResult,
     XFormsModelSubmission.SubmissionParameters p,
     XFormsModelSubmission.SecondPassParameters p2)
     throws Exception {
   // Deserialize here so it can run in parallel
   if (XMLUtils.isXMLMediatype(connectionResult.getResponseMediaType())) {
     // XML media type
     final IndentedLogger detailsLogger = getDetailsLogger(p, p2);
     resultingDocument =
         deserializeInstance(
             propertyContext, detailsLogger, p2.isReadonly, p2.isHandleXInclude, connectionResult);
   } else {
     // Other media type is not allowed
     throw new XFormsSubmissionException(
         submission,
         "Body received with non-XML media type for replace=\"instance\": "
             + connectionResult.getResponseMediaType(),
         "processing instance replacement",
         new XFormsSubmitErrorEvent(
             containingDocument,
             propertyContext,
             submission,
             XFormsSubmitErrorEvent.ErrorType.RESOURCE_ERROR,
             connectionResult));
   }
 }
Beispiel #11
0
 public static XMLReader newXMLReader(XMLUtils.ParserConfiguration parserConfiguration) {
   final SAXParser saxParser = XMLUtils.newSAXParser(parserConfiguration);
   try {
     final XMLReader xmlReader = saxParser.getXMLReader();
     xmlReader.setEntityResolver(XMLUtils.ENTITY_RESOLVER);
     xmlReader.setErrorHandler(XMLUtils.ERROR_HANDLER);
     return xmlReader;
   } catch (Exception e) {
     throw new OXFException(e);
   }
 }
Beispiel #12
0
  private static void mapPrefixIfNeeded(
      Set<String> declaredPrefixes, String uri, String qName, StringBuilder sb) {
    final String prefix = XMLUtils.prefixFromQName(qName);
    if (prefix.length() > 0 && !declaredPrefixes.contains(prefix)) {
      sb.append(" xmlns:");
      sb.append(prefix);
      sb.append("=\"");
      sb.append(uri);
      sb.append("\"");

      declaredPrefixes.add(prefix);
    }
  }
  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);
  }
Beispiel #14
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;
  }
  private Attributes getAttributesWithClass(Attributes originalAttributes) {
    String newClassAttribute = originalAttributes.getValue("class");

    if (addedClasses != null && addedClasses.length() > 0) {
      if (newClassAttribute == null || newClassAttribute.length() == 0) {
        newClassAttribute = addedClasses;
      } else {
        newClassAttribute += " " + addedClasses;
      }
    }

    if (newClassAttribute != null)
      return XMLUtils.addOrReplaceAttribute(originalAttributes, "", "", "class", newClassAttribute);
    else return originalAttributes;
  }
 /** Transform an InputStream to a dom4j Document. */
 public static Document readDom4j(
     InputStream inputStream, String systemId, boolean handleXInclude, boolean handleLexical) {
   final LocationSAXContentHandler dom4jResult = new LocationSAXContentHandler();
   {
     final XMLReceiver xmlReceiver;
     if (handleXInclude) {
       // Insert XIncludeContentHandler
       xmlReceiver =
           new XIncludeProcessor.XIncludeXMLReceiver(
               null,
               dom4jResult,
               null,
               new TransformerURIResolver(XMLUtils.ParserConfiguration.PLAIN));
     } else {
       xmlReceiver = dom4jResult;
     }
     XMLUtils.inputStreamToSAX(
         inputStream, systemId, xmlReceiver, XMLUtils.ParserConfiguration.PLAIN, handleLexical);
   }
   return dom4jResult.getDocument();
 }
  @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);
  }
Beispiel #18
0
  /**
   * Return the charset associated with a text/* Content-Type header. If a charset is present,
   * return it. Otherwise, guess depending on whether the mediatype is text/xml or not.
   *
   * @param contentType Content-Type header value
   * @return charset
   */
  public static String getTextCharsetFromContentType(String contentType) {
    final String charset;
    final String connectionCharset = getContentTypeCharset(contentType);
    if (connectionCharset != null) {
      charset = connectionCharset;
    } else {

      // RFC 3023: "Conformant with [RFC2046], if a text/xml entity is
      // received with the charset parameter omitted, MIME processors and
      // XML processors MUST use the default charset value of
      // "us-ascii"[ASCII]. In cases where the XML MIME entity is
      // transmitted via HTTP, the default charset value is still
      // "us-ascii". (Note: There is an inconsistency between this
      // specification and HTTP/1.1, which uses ISO-8859-1[ISO8859] as the
      // default for a historical reason. Since XML is a new format, a new
      // default should be chosen for better I18N. US-ASCII was chosen,
      // since it is the intersection of UTF-8 and ISO-8859-1 and since it
      // is already used by MIME.)"

      if (XMLUtils.isXMLMediatype(contentType)) charset = DEFAULT_TEXT_XML_READING_ENCODING;
      else charset = DEFAULT_HTTP_TEXT_READING_ENCODING;
    }
    return charset;
  }
  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++;
  }
  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);
  }
  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);
    }
  }
  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 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 static void outputResponseDocument(
      final PipelineContext pipelineContext,
      final ExternalContext externalContext,
      final IndentedLogger indentedLogger,
      final SAXStore annotatedDocument,
      final XFormsContainingDocument containingDocument,
      final XMLReceiver xmlReceiver)
      throws SAXException, IOException {

    final List<XFormsContainingDocument.Load> loads = containingDocument.getLoadsToRun();
    if (containingDocument.isGotSubmissionReplaceAll()) {
      // 1. Got a submission with replace="all"

      // NOP: Response already sent out by a submission
      // TODO: modify XFormsModelSubmission accordingly
      indentedLogger.logDebug("", "handling response for submission with replace=\"all\"");
    } else if (loads != null && loads.size() > 0) {
      // 2. Got at least one xforms:load

      // Send redirect out

      // Get first load only
      final XFormsContainingDocument.Load load = loads.get(0);

      // Send redirect
      final String redirectResource = load.getResource();
      indentedLogger.logDebug(
          "", "handling redirect response for xforms:load", "url", redirectResource);
      // Set isNoRewrite to true, because the resource is either a relative path or already contains
      // the servlet context
      externalContext.getResponse().sendRedirect(redirectResource, null, false, false, true);

      // Still send out a null document to signal that no further processing must take place
      XMLUtils.streamNullDocument(xmlReceiver);
    } else {
      // 3. Regular case: produce an XHTML document out

      final ElementHandlerController controller = new ElementHandlerController();

      // Register handlers on controller (the other handlers are registered by the body handler)
      {
        controller.registerHandler(
            XHTMLHeadHandler.class.getName(), XMLConstants.XHTML_NAMESPACE_URI, "head");
        controller.registerHandler(
            XHTMLBodyHandler.class.getName(), XMLConstants.XHTML_NAMESPACE_URI, "body");

        // Register a handler for AVTs on HTML elements
        final boolean hostLanguageAVTs =
            XFormsProperties
                .isHostLanguageAVTs(); // TODO: this should be obtained per document, but we only
        // know about this in the extractor
        if (hostLanguageAVTs) {
          controller.registerHandler(
              XXFormsAttributeHandler.class.getName(),
              XFormsConstants.XXFORMS_NAMESPACE_URI,
              "attribute");
          controller.registerHandler(
              XHTMLElementHandler.class.getName(), XMLConstants.XHTML_NAMESPACE_URI);
        }

        // Swallow XForms elements that are unknown
        controller.registerHandler(
            NullHandler.class.getName(), XFormsConstants.XFORMS_NAMESPACE_URI);
        controller.registerHandler(
            NullHandler.class.getName(), XFormsConstants.XXFORMS_NAMESPACE_URI);
        controller.registerHandler(NullHandler.class.getName(), XFormsConstants.XBL_NAMESPACE_URI);
      }

      // Set final output
      controller.setOutput(new DeferredXMLReceiverImpl(xmlReceiver));
      // Set handler context
      controller.setElementHandlerContext(
          new HandlerContext(
              controller, pipelineContext, containingDocument, externalContext, null));
      // Process the entire input
      annotatedDocument.replay(
          new ExceptionWrapperXMLReceiver(controller, "converting XHTML+XForms document to XHTML"));
    }

    containingDocument.afterInitialResponse();
  }