<commit_before>/**
 * Copyright (C) 2009 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms.processor.handlers;

import org.orbeon.oxf.xforms.XFormsConstants;
import org.orbeon.oxf.xforms.control.XFormsControl;
import org.orbeon.oxf.xforms.control.XFormsSingleNodeControl;
import org.orbeon.oxf.xforms.processor.XFormsElementFilterContentHandler;
import org.orbeon.oxf.xml.*;
import org.orbeon.saxon.om.FastStringBuffer;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

/**
 * Handle xforms:group.
 */
public class XFormsGroupHandler extends XFormsControlLifecyleHandler {

    // Appearances
    private boolean isFieldsetAppearance;
    private boolean isInternalAppearance;

    private boolean isGroupInTable;

    // Label information gathered during prepareHandler()
    private String labelValue;
    private FastStringBuffer labelClasses;

    private DeferredContentHandler currentSavedOutput;
    private OutputInterceptor outputInterceptor;

    private static final String XHTML_PREFIX = "{" + XMLConstants.XHTML_NAMESPACE_URI + "}";
    private static final int XHTML_PREFIX_LENGTH = XHTML_PREFIX.length();
    private static final Map TABLE_CONTAINERS  = new HashMap();

    static {
        TABLE_CONTAINERS.put("table", "");
        TABLE_CONTAINERS.put("tbody", "");
        TABLE_CONTAINERS.put("thead", "");
        TABLE_CONTAINERS.put("tfoot", "");
        TABLE_CONTAINERS.put("tr", "");
    }

    public XFormsGroupHandler() {
        super(false, true);
    }

    protected void prepareHandler(String uri, String localname, String qName, Attributes attributes, String staticId, String effectiveId, XFormsSingleNodeControl xformsControl) {
        // Special appearance that does not output any HTML. This is temporary until xforms:group is correctly supported within xforms:repeat.
        isInternalAppearance = XFormsConstants.XXFORMS_INTERNAL_APPEARANCE_QNAME.equals(getAppearance(attributes));
        if (isInternalAppearance)
            return;

        isFieldsetAppearance = XFormsConstants.XXFORMS_FIELDSET_APPEARANCE_QNAME.equals(getAppearance(attributes));

        // Determine whether the closest xhtml:* parent is xhtml:table|xhtml:tbody|xhtml:thead|xhtml:tfoot|xhtml:tr
        final ElementHandlerController controller = handlerContext.getController();
        {
            final Stack elementNames = controller.getElementNames();
            for (int i = elementNames.size() - 1; i >= 0; i--) {
                final String currentElementName = (String) elementNames.get(i);
                if (currentElementName.startsWith(XHTML_PREFIX)) {
                    final String currentLocalName = currentElementName.substring(XHTML_PREFIX_LENGTH);
                    isGroupInTable = (TABLE_CONTAINERS.get(currentLocalName) != null);
                    break;
                }
            }
        }

        if (!isGroupInTable) {
            // Group outside table

            // Gather information about label value and classes

            // Value
            if (handlerContext.isTemplate() || xformsControl == null) {
                labelValue = null;
            } else {
                labelValue = xformsControl.getLabel(pipelineContext);
            }

            // Label
            final boolean hasLabel = XFormsControl.hasLabel(containingDocument, getPrefixedId());
            if (hasLabel) {
                labelClasses = new FastStringBuffer("xforms-label");

                // Handle relevance on label
                if ((xformsControl == null && !handlerContext.isTemplate()) || (xformsControl != null && !xformsControl.isRelevant())) {
                    labelClasses.append(" xforms-disabled");
                }

                // Copy over existing label classes if any
                final String labelClassAttribute = containingDocument.getStaticState().getLabelElement(getPrefixedId()).attributeValue("class");
                if (labelClassAttribute != null) {
                    labelClasses.append(' ');
                    labelClasses.append(labelClassAttribute);
                }
            }
        }
    }

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

        // No additional markup for internal appearance
        if (isInternalAppearance)
            return;

        // Find classes to add
        final FastStringBuffer classes = getInitialClasses(uri, localname, attributes, null);
        handleMIPClasses(classes, getPrefixedId(), xformsControl);

        // Start xhtml:span or xhtml:fieldset
        final String groupElementName = isFieldsetAppearance ? "fieldset" : "span";
        final String xhtmlPrefix = handlerContext.findXHTMLPrefix();
        final String groupElementQName = XMLUtils.buildQName(xhtmlPrefix, groupElementName);

        final ElementHandlerController controller = handlerContext.getController();

        if (!isGroupInTable) {
            // Group outside table

            final ContentHandler contentHandler = controller.getOutput();

            if (isFieldsetAppearance) {
                // Fieldset appearance

                // Start xhtml:fieldset element
                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, labelClasses.toString());
                    reusableAttributes.addAttribute("", "id", "id", ContentHandlerHelper.CDATA, effectiveId + "-label");

                    // Output xhtml:legend with label content
                    final String legendQName = XMLUtils.buildQName(xhtmlPrefix, "legend");
                    contentHandler.startElement(XMLConstants.XHTML_NAMESPACE_URI, "legend", legendQName, reusableAttributes);
                    if (labelValue != null && !labelValue.equals(""))
                        contentHandler.characters(labelValue.toCharArray(), 0, labelValue.length());
                    contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "legend", legendQName);
                }
            } else {
                // Default appearance

                // Label is handled by handleLabel()

                // Start xhtml:span element
                contentHandler.startElement(XMLConstants.XHTML_NAMESPACE_URI, groupElementName, groupElementQName, getAttributes(attributes, classes.toString(), effectiveId));
            }
        } else {
            // Group within table

            // 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()) {
                outputInterceptor = new OutputInterceptor(currentSavedOutput, groupElementQName, new OutputInterceptor.Listener() {
                    public void generateFirstDelimiter(OutputInterceptor outputInterceptor) throws SAXException {
                        // Delimiter: begin group
                        outputInterceptor.outputDelimiter(currentSavedOutput, outputInterceptor.getDelimiterNamespaceURI(),
                                outputInterceptor.getDelimiterPrefix(), outputInterceptor.getDelimiterLocalName(), "xforms-group-begin-end", "group-begin-" + effectiveId);
                    }
                });
                // TODO: is the use of XFormsElementFilterContentHandler necessary now?
                controller.setOutput(new DeferredContentHandlerImpl(new XFormsElementFilterContentHandler(outputInterceptor)));

                // Set control classes
                outputInterceptor.setAddedClasses(classes);
            } else if (isDisabled(xformsControl)) {
                // Group not visible, set output to a black hole
                handlerContext.getController().setOutput(new DeferredContentHandlerAdapter());
            }

            // Don't support label, help, alert, or hint and other appearances, only the content!
        }
    }

    public void handleControlEnd(String uri, String localname, String qName, Attributes attributes, String staticId, String effectiveId, XFormsSingleNodeControl xformsControl) throws SAXException {

        // No additional markup for internal appearance
        if (isInternalAppearance)
            return;

        final ElementHandlerController controller = handlerContext.getController();
        if (!isGroupInTable) {
            // Group outside table

            // Close xhtml:span
            final String xhtmlPrefix = handlerContext.findXHTMLPrefix();<commit_msg>Put MIP classes on groups within table as well.<commit_after>/**
 * Copyright (C) 2009 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.xforms.processor.handlers;

import org.orbeon.oxf.xforms.XFormsConstants;
import org.orbeon.oxf.xforms.control.XFormsControl;
import org.orbeon.oxf.xforms.control.XFormsSingleNodeControl;
import org.orbeon.oxf.xforms.processor.XFormsElementFilterContentHandler;
import org.orbeon.oxf.xml.*;
import org.orbeon.saxon.om.FastStringBuffer;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

import java.util.HashMap;
import java.util.Map;
import java.util.Stack;

/**
 * Handle xforms:group.
 */
public class XFormsGroupHandler extends XFormsControlLifecyleHandler {

    // Appearances
    private boolean isFieldsetAppearance;
    private boolean isInternalAppearance;

    private boolean isGroupInTable;

    // Label information gathered during prepareHandler()
    private String labelValue;
    private FastStringBuffer labelClasses;

    private DeferredContentHandler currentSavedOutput;
    private OutputInterceptor outputInterceptor;

    private static final String XHTML_PREFIX = "{" + XMLConstants.XHTML_NAMESPACE_URI + "}";
    private static final int XHTML_PREFIX_LENGTH = XHTML_PREFIX.length();
    private static final Map TABLE_CONTAINERS  = new HashMap();

    static {
        TABLE_CONTAINERS.put("table", "");
        TABLE_CONTAINERS.put("tbody", "");
        TABLE_CONTAINERS.put("thead", "");
        TABLE_CONTAINERS.put("tfoot", "");
        TABLE_CONTAINERS.put("tr", "");
    }

    public XFormsGroupHandler() {
        super(false, true);
    }

    protected void prepareHandler(String uri, String localname, String qName, Attributes attributes, String staticId, String effectiveId, XFormsSingleNodeControl xformsControl) {
        // Special appearance that does not output any HTML. This is temporary until xforms:group is correctly supported within xforms:repeat.
        isInternalAppearance = XFormsConstants.XXFORMS_INTERNAL_APPEARANCE_QNAME.equals(getAppearance(attributes));
        if (isInternalAppearance)
            return;

        isFieldsetAppearance = XFormsConstants.XXFORMS_FIELDSET_APPEARANCE_QNAME.equals(getAppearance(attributes));

        // Determine whether the closest xhtml:* parent is xhtml:table|xhtml:tbody|xhtml:thead|xhtml:tfoot|xhtml:tr
        final ElementHandlerController controller = handlerContext.getController();
        {
            final Stack elementNames = controller.getElementNames();
            for (int i = elementNames.size() - 1; i >= 0; i--) {
                final String currentElementName = (String) elementNames.get(i);
                if (currentElementName.startsWith(XHTML_PREFIX)) {
                    final String currentLocalName = currentElementName.substring(XHTML_PREFIX_LENGTH);
                    isGroupInTable = (TABLE_CONTAINERS.get(currentLocalName) != null);
                    break;
                }
            }
        }

        if (!isGroupInTable) {
            // Group outside table

            // Gather information about label value and classes

            // Value
            if (handlerContext.isTemplate() || xformsControl == null) {
                labelValue = null;
            } else {
                labelValue = xformsControl.getLabel(pipelineContext);
            }

            // Label
            final boolean hasLabel = XFormsControl.hasLabel(containingDocument, getPrefixedId());
            if (hasLabel) {
                labelClasses = new FastStringBuffer("xforms-label");

                // Handle relevance on label
                if ((xformsControl == null && !handlerContext.isTemplate()) || (xformsControl != null && !xformsControl.isRelevant())) {
                    labelClasses.append(" xforms-disabled");
                }

                // Copy over existing label classes if any
                final String labelClassAttribute = containingDocument.getStaticState().getLabelElement(getPrefixedId()).attributeValue("class");
                if (labelClassAttribute != null) {
                    labelClasses.append(' ');
                    labelClasses.append(labelClassAttribute);
                }
            }
        }
    }

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

        // No additional markup for internal appearance
        if (isInternalAppearance)
            return;

        // Start xhtml:span or xhtml:fieldset
        final String groupElementName = isFieldsetAppearance ? "fieldset" : "span";
        final String xhtmlPrefix = handlerContext.findXHTMLPrefix();
        final String groupElementQName = XMLUtils.buildQName(xhtmlPrefix, groupElementName);

        final ElementHandlerController controller = handlerContext.getController();

        if (!isGroupInTable) {
            // Group outside table

            // Get classes
            final FastStringBuffer classes = getInitialClasses(uri, localname, attributes, null);
            handleMIPClasses(classes, getPrefixedId(), xformsControl);

            final ContentHandler contentHandler = controller.getOutput();

            if (isFieldsetAppearance) {
                // Fieldset appearance

                // Start xhtml:fieldset element
                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, labelClasses.toString());
                    reusableAttributes.addAttribute("", "id", "id", ContentHandlerHelper.CDATA, effectiveId + "-label");

                    // Output xhtml:legend with label content
                    final String legendQName = XMLUtils.buildQName(xhtmlPrefix, "legend");
                    contentHandler.startElement(XMLConstants.XHTML_NAMESPACE_URI, "legend", legendQName, reusableAttributes);
                    if (labelValue != null && !labelValue.equals(""))
                        contentHandler.characters(labelValue.toCharArray(), 0, labelValue.length());
                    contentHandler.endElement(XMLConstants.XHTML_NAMESPACE_URI, "legend", legendQName);
                }
            } else {
                // Default appearance

                // Label is handled by handleLabel()

                // Start xhtml:span element
                contentHandler.startElement(XMLConstants.XHTML_NAMESPACE_URI, groupElementName, groupElementQName, getAttributes(attributes, classes.toString(), effectiveId));
            }
        } else {
            // Group within table

            // Get classes for the first delimiter
            // As of August 2009, actually only need the marker class as well as xforms-disabled if the group is non-relevant
            final FastStringBuffer classes = new FastStringBuffer("xforms-group-begin-end");
            handleMIPClasses(classes, getPrefixedId(), xformsControl);

            // 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()) {
                outputInterceptor = new OutputInterceptor(currentSavedOutput, groupElementQName, new OutputInterceptor.Listener() {
                    public void generateFirstDelimiter(OutputInterceptor outputInterceptor) throws SAXException {
                        // Delimiter: begin group
                        outputInterceptor.outputDelimiter(currentSavedOutput, outputInterceptor.getDelimiterNamespaceURI(),
                                outputInterceptor.getDelimiterPrefix(), outputInterceptor.getDelimiterLocalName(), classes.toString(), "group-begin-" + effectiveId);
                    }
                });
                // TODO: is the use of XFormsElementFilterContentHandler necessary now?
                controller.setOutput(new DeferredContentHandlerImpl(new XFormsElementFilterContentHandler(outputInterceptor)));

                // Set control classes
                outputInterceptor.setAddedClasses(classes);
            } else if (isDisabled(xformsControl)) {
                // Group not visible, set output to a black hole
                handlerContext.getController().setOutput(new DeferredContentHandlerAdapter());
            }

            // Don't support label, help, alert, or hint and other appearances, only the content!
        }
    }

    public void handleControlEnd(String uri, String localname, String qName, Attributes attributes, String staticId, String effectiveId, XFormsSingleNodeControl xformsControl) throws SAXException {

        // No additional markup for internal appearance
        if (isInternalAppearance)
            return;

        final ElementHandlerController controller = handlerContext.getController();
        if (!isGroupInTable) {
            // Group outside table

            // Close xhtml:span
            final String xhtmlPrefix = handlerContext.findXHTMLPrefix();
{'fraction_non_alphanumeric': 0.0811717131867048, 'fraction_numerical': 0.0010849196173192623, 'mean_word_length': 2.5752820874471087, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 8}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3438382', 'n_tokens_mistral': 4976, 'n_tokens_neox': 4759, 'n_words': 1314}