/**
* Copyright (C) 2010 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.*;
import org.orbeon.oxf.xforms.analysis.ElementAnalysis;
import org.orbeon.oxf.xforms.analysis.controls.AttributeControl;
import org.orbeon.oxf.xforms.analysis.controls.LHHAAnalysis;
import org.orbeon.oxf.xforms.control.XFormsControl;
import org.orbeon.oxf.xforms.control.XFormsSingleNodeControl;
import org.orbeon.oxf.xforms.control.controls.XXFormsAttributeControl;
import org.orbeon.oxf.xml.*;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import java.util.HashMap;
import java.util.Map;
/**
* Base XForms handler used as base class in both the xml and xhtml handlers.
*/
public abstract class XFormsBaseHandler extends ElementHandler {
public enum LHHAC {
LABEL, HELP, HINT, ALERT, CONTROL
}
public static final Map<LHHAC, String> LHHAC_CODES = new HashMap<LHHAC, String>();
static {
LHHAC_CODES.put(LHHAC.LABEL, "l");
LHHAC_CODES.put(LHHAC.HELP, "p");
LHHAC_CODES.put(LHHAC.HINT, "t");
LHHAC_CODES.put(LHHAC.ALERT, "a");
LHHAC_CODES.put(LHHAC.CONTROL, "c");
// "i" is also used for help image
}
private final boolean repeating;
private final boolean forwarding;
protected HandlerContext handlerContext;
protected ElementAnalysis elementAnalysis;
protected XFormsContainingDocument containingDocument;
protected AttributesImpl reusableAttributes = new AttributesImpl();
protected XFormsBaseHandler(boolean repeating, boolean forwarding) {
this.repeating = repeating;
this.forwarding = forwarding;
}
@Override
public void init(String uri, String localname, String qName, Attributes attributes, Object matched) throws SAXException {
// All elements corresponding to XForms controls must cause elementAnalysis to be available
if (matched instanceof ElementAnalysis)
this.elementAnalysis = (ElementAnalysis) matched;
}
public void setContext(Object context) {
this.handlerContext = (HandlerContext) context;
this.containingDocument = handlerContext.getContainingDocument();
super.setContext(context);
}
public boolean isRepeating() {
return repeating;
}
public boolean isForwarding() {
return forwarding;
}
/**
* Whether the control is disabled in the resulting HTML. Occurs when:
*
* o control is readonly but not static readonly
*
* @param control control to check or null if no concrete control available
* @return whether the control is to be marked as disabled
*/
public boolean isHTMLDisabled(XFormsControl control) {
return (control instanceof XFormsSingleNodeControl)
&& ((XFormsSingleNodeControl) control).isReadonly()
&& ! containingDocument.staticReadonly();
}
public boolean isNonRelevant(XFormsControl control) {
return control == null || ! control.isRelevant();
}
public static void handleAccessibilityAttributes(Attributes srcAttributes, AttributesImpl destAttributes) {
// Handle "tabindex"
{
// This is the standard XForms attribute
String value = srcAttributes.getValue("navindex");
if (value == null) {
// Try the the XHTML attribute
value = srcAttributes.getValue("tabindex");
}
if (value != null)
destAttributes.addAttribute("", "tabindex", "tabindex", XMLReceiverHelper.CDATA, value);
}
// Handle "accesskey"
{
final String value = srcAttributes.getValue("accesskey");
if (value != null)
destAttributes.addAttribute("", "accesskey", "accesskey", XMLReceiverHelper.CDATA, value);
}
// Handle "role"
{
final String value = srcAttributes.getValue("role");
if (value != null)
destAttributes.addAttribute("", "role", "role", XMLReceiverHelper.CDATA, value);
}
}
public static void handleAriaAttributes(boolean required, boolean valid, AttributesImpl destAttributes) {
if (required)
destAttributes.addAttribute("", "aria-required", "aria-required", XMLReceiverHelper.CDATA, "true");
if (! valid)
destAttributes.addAttribute("", "aria-invalid", "aria-invalid", XMLReceiverHelper.CDATA, "true");
}
protected AttributesImpl getIdClassXHTMLAttributes(Attributes elementAttributes, String classes, String effectiveId) {
return getIdClassXHTMLAttributes(containingDocument, reusableAttributes, elementAttributes, classes, effectiveId);
}
protected static AttributesImpl getIdClassXHTMLAttributes(XFormsContainingDocument containingDocument, AttributesImpl reusableAttributes, Attributes elementAttributes, String classes, String effectiveId) {
reusableAttributes.clear();
// Copy "id"
if (effectiveId != null) {
reusableAttributes.addAttribute("", "id", "id", XMLReceiverHelper.CDATA, XFormsUtils.namespaceId(containingDocument, effectiveId));
}
// Create "class" attribute if necessary
{
if (classes != null && classes.length() > 0) {
reusableAttributes.addAttribute("", "class", "class", XMLReceiverHelper.CDATA, classes);
}
}
// Copy attributes in the xhtml namespace to no namespace
for (int i = 0; i < elementAttributes.getLength(); i++) {
if (XMLConstants.XHTML_NAMESPACE_URI.equals(elementAttributes.getURI(i))) {
final String name = elementAttributes.getLocalName(i);
if (!"class".equals(name)) {
reusableAttributes.addAttribute("", name, name, XMLReceiverHelper.CDATA, elementAttributes.getValue(i));
}
}
}
return reusableAttributes;
}
public static boolean isStaticReadonly(XFormsControl control) {
return control != null && control.isStaticReadonly();
}
public static String getLHHACId(XFormsContainingDocument containingDocument, String controlEffectiveId, String suffix) {
// E.g. foo$bar.1-2-3 -> foo$bar$$alert.1-2-3
return XFormsUtils.namespaceId(containingDocument, XFormsUtils.appendToEffectiveId(controlEffectiveId, XFormsConstants.LHHAC_SEPARATOR + suffix));
}
protected Attributes handleAVTsAndIDs(Attributes attributes, String[] refIdAttributeNames) {
final String prefixedId = handlerContext.getPrefixedId(attributes);
if (prefixedId != null) {
final boolean hasAVT = containingDocument.getStaticOps().hasAttributeControl(prefixedId);
final String effectiveId = handlerContext.getEffectiveId(attributes);
boolean found = false;
if (hasAVT) {
// This element has at least one AVT so process its attributes
final int attributesCount = attributes.getLength();
for (int i = 0; i < attributesCount; i++) {
final String attributeValue = attributes.getValue(i);
if (XFormsUtils.maybeAVT(attributeValue)) {
// This is an AVT most likely
found = true;
final String attributeLocalName = attributes.getLocalName(i);
final String attributeQName = attributes.getQName(i);// use qualified name so we match on "xml:lang"
// Control analysis
final AttributeControl controlAnalysis = containingDocument.getStaticOps().getAttributeControl(prefixedId, attributeQName);
// Get static id of attribute control associated with this particular attribute
final String attributeControlStaticId = controlAnalysis.staticId();
// Find concrete control if possible
final XXFormsAttributeControl attributeControl;
if (handlerContext.isTemplate()) {
attributeControl = null;
} else if (attributeControlStaticId != null) {
final String attributeControlEffectiveId = XFormsUtils.getRelatedEffectiveId(effectiveId, attributeControlStaticId);
attributeControl = (XXFormsAttributeControl) containingDocument.getControlByEffectiveId(attributeControlEffectiveId);
} else {
// This should not happen
attributeControl = null;
}
// Determine attribute value
// NOTE: This also handles dummy images for the xhtml:img/@src case
final String effectiveAttributeValue = XXFormsAttributeControl.getExternalValueHandleSrc(attributeControl, controlAnalysis);
// Set the value of the attribute
attributes = SAXUtils.addOrReplaceAttribute(attributes, attributes.getURI(i),
XMLUtils.prefixFromQName(attributeQName), attributeLocalName, effectiveAttributeValue);
}
}
if (found) {
// Update the value of the id attribute
attributes = SAXUtils.addOrReplaceAttribute(attributes, "", "", "id", XFormsUtils.namespaceId(containingDocument, effectiveId));
}
}
if (!found) {
// Id was not replaced as part of AVT processing
// Update the value of the id attribute
attributes = SAXUtils.addOrReplaceAttribute(attributes, "", "", "id", XFormsUtils.namespaceId(containingDocument, effectiveId));
}
}
// Check @for or other attribute
for (String refIdAttributeName : refIdAttributeNames)
{
final String forAttribute = attributes.getValue(refIdAttributeName);
if (forAttribute != null) {
attributes = SAXUtils.addOrReplaceAttribute(attributes, "", "", refIdAttributeName, handlerContext.getIdPrefix() + forAttribute + handlerContext.getIdPostfix());
}
}
return attributes;
}
protected LHHAAnalysis getStaticLHHA(String controlPrefixedId, XFormsBaseHandler.LHHAC lhhaType) {
final StaticStateGlobalOps globalOps = containingDocument.getStaticOps();
if (lhhaType == LHHAC.ALERT)
return globalOps.getAlerts(controlPrefixedId).head(); // for alerts, take the first one, but does this make sense?
else
return globalOps.getLHH(controlPrefixedId, lhhaType.name().toLowerCase());
}
}