| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.cocoon.forms.transformation; |
| |
| import org.apache.cocoon.forms.FormsConstants; |
| import org.apache.cocoon.forms.event.ValueChangedListenerEnabled; |
| import org.apache.cocoon.forms.formmodel.AggregateField; |
| import org.apache.cocoon.forms.formmodel.DataWidget; |
| import org.apache.cocoon.forms.formmodel.Group; |
| import org.apache.cocoon.forms.formmodel.Repeater; |
| import org.apache.cocoon.forms.formmodel.Struct; |
| import org.apache.cocoon.forms.formmodel.Union; |
| import org.apache.cocoon.forms.formmodel.Widget; |
| import org.apache.cocoon.forms.validation.ValidationError; |
| import org.apache.cocoon.forms.validation.ValidationErrorAware; |
| import org.apache.cocoon.i18n.I18nUtils; |
| import org.apache.cocoon.xml.AbstractXMLPipe; |
| import org.apache.cocoon.xml.AttributesImpl; |
| import org.apache.cocoon.xml.SaxBuffer; |
| import org.apache.cocoon.xml.XMLUtils; |
| |
| import org.apache.commons.jxpath.JXPathException; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.ext.LexicalHandler; |
| |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| /** |
| * The basic operation of this Pipe is that it replaces <code>ft:widget</code> |
| * (in the {@link FormsConstants#TEMPLATE_NS} namespace) tags (having an id attribute) |
| * by the XML representation of the corresponding widget instance. |
| * |
| * <p>These XML fragments (normally all in the {@link FormsConstants#INSTANCE_NS "CForms Instance"} |
| * namespace), can then be translated to a HTML presentation by an XSLT. |
| * This XSLT will then only have to style individual widget, and will not |
| * need to do the whole page layout. |
| * |
| * <p>For more information about the supported tags and their function, |
| * see the user documentation for the forms template transformer.</p> |
| * |
| * @version $Id$ |
| */ |
| public class EffectWidgetReplacingPipe extends EffectPipe { |
| |
| /** |
| * Form location attribute on <code>ft:form-template</code> element, containing |
| * JXPath expression which should result in Form object. |
| * |
| * @see FormsPipelineConfig#findForm(String) |
| */ |
| private static final String LOCATION = "location"; |
| |
| private static final String AGGREGATE_WIDGET = "aggregate-widget"; |
| private static final String CHOOSE = "choose"; |
| private static final String CLASS = "class"; |
| private static final String CONTINUATION_ID = "continuation-id"; |
| private static final String FORM_TEMPLATE_EL = "form-template"; |
| private static final String GROUP = "group"; |
| private static final String NEW = "new"; |
| private static final String REPEATER = "repeater"; |
| private static final String REPEATER_ROWS = "repeater-rows"; |
| private static final String REPEATER_SIZE = "repeater-size"; |
| private static final String REPEATER_WIDGET = "repeater-widget"; |
| private static final String REPEATER_WIDGET_LABEL = "repeater-widget-label"; |
| private static final String STRUCT = "struct"; |
| private static final String STYLING_EL = "styling"; |
| private static final String UNION = "union"; |
| private static final String VALIDATION_ERROR = "validation-error"; |
| private static final String WIDGET = "widget"; |
| private static final String WIDGET_LABEL = "widget-label"; |
| |
| private final AggregateWidgetHandler hAggregate = new AggregateWidgetHandler(); |
| private final ChooseHandler hChoose = new ChooseHandler(); |
| protected final ChoosePassThruHandler hChoosePassThru = new ChoosePassThruHandler(); |
| private final ClassHandler hClass = new ClassHandler(); |
| private final ContinuationIdHandler hContinuationId = new ContinuationIdHandler(); |
| private final DocHandler hDocument = new DocHandler(); |
| protected final FormHandler hForm = new FormHandler(); |
| private final GroupHandler hGroup = new GroupHandler(); |
| protected final NestedHandler hNested = new NestedHandler(); |
| private final NewHandler hNew = new NewHandler(); |
| private final RepeaterSizeHandler hRepeaterSize = new RepeaterSizeHandler(); |
| private final RepeaterHandler hRepeater = new RepeaterHandler(); |
| private final RepeaterRowsHandler hRepeaterRows = new RepeaterRowsHandler(); |
| private final RepeaterWidgetHandler hRepeaterWidget = new RepeaterWidgetHandler(); |
| private final RepeaterWidgetLabelHandler hRepeaterWidgetLabel = new RepeaterWidgetLabelHandler(); |
| protected final SkipHandler hSkip = new SkipHandler(); |
| private final StructHandler hStruct = new StructHandler(); |
| protected final StylingContentHandler hStyling = new StylingContentHandler(); |
| private final UnionHandler hUnion = new UnionHandler(); |
| protected final UnionPassThruHandler hUnionPassThru = new UnionPassThruHandler(); |
| private final ValidationErrorHandler hValidationError = new ValidationErrorHandler(); |
| private final WidgetHandler hWidget = new WidgetHandler(); |
| private final WidgetLabelHandler hWidgetLabel = new WidgetLabelHandler(); |
| |
| /** |
| * Map containing all handlers |
| */ |
| protected final Map templates; |
| |
| protected FormsPipelineConfig pipeContext; |
| |
| /** |
| * The namespaces and their prefixes |
| */ |
| private final List namespaces; |
| |
| /** |
| * True if instance namespace has been mapped to the |
| * 'fi' prefix. |
| */ |
| protected boolean hasInstanceNamespace; |
| |
| protected Widget contextWidget; |
| protected LinkedList contextWidgets; |
| protected LinkedList chooseWidgets; |
| protected Widget widget; |
| protected Map classes; |
| |
| |
| public EffectWidgetReplacingPipe() { |
| namespaces = new ArrayList(5); |
| // Setup map of templates. |
| templates = new HashMap(); |
| templates.put(AGGREGATE_WIDGET, hAggregate); |
| templates.put(CHOOSE, hChoose); |
| templates.put(CLASS, hClass); |
| templates.put(CONTINUATION_ID, hContinuationId); |
| templates.put(GROUP, hGroup); |
| templates.put(NEW, hNew); |
| templates.put(REPEATER, hRepeater); |
| templates.put(REPEATER_ROWS, hRepeaterRows); |
| templates.put(REPEATER_SIZE, hRepeaterSize); |
| templates.put(REPEATER_WIDGET, hRepeaterWidget); |
| templates.put(REPEATER_WIDGET_LABEL, hRepeaterWidgetLabel); |
| templates.put(STRUCT, hStruct); |
| templates.put(UNION, hUnion); |
| templates.put(VALIDATION_ERROR, hValidationError); |
| templates.put(WIDGET, hWidget); |
| templates.put(WIDGET_LABEL, hWidgetLabel); |
| } |
| |
| public void init(Widget contextWidget, FormsPipelineConfig pipeContext) { |
| // Document handler is top level handler |
| super.init(hDocument); |
| this.pipeContext = pipeContext; |
| |
| // Initialize widget related variables |
| this.contextWidgets = new LinkedList(); |
| this.chooseWidgets = new LinkedList(); |
| this.classes = new HashMap(); |
| } |
| |
| public void recycle() { |
| super.recycle(); |
| this.contextWidget = null; |
| this.widget = null; |
| this.pipeContext = null; |
| this.namespaces.clear(); |
| this.hasInstanceNamespace = false; |
| } |
| |
| /** |
| * Get value of the required attribute |
| */ |
| protected String getAttributeValue(String loc, Attributes attrs, String name) throws SAXException { |
| String value = attrs.getValue(name); |
| if (value == null) { |
| throw new SAXException("Element '" + loc + "' missing required '" + name + "' attribute, " + |
| "at " + getLocation()); |
| } |
| return value; |
| } |
| |
| /** |
| * Get non-empty value of the required attribute |
| */ |
| protected String getRequiredAttributeValue(String loc, Attributes attrs, String name) throws SAXException { |
| String value = attrs.getValue(name); |
| if (value == null || value.length() == 0) { |
| throw new SAXException("Element '" + loc + "' missing required '" + name + "' attribute, " + |
| "at " + getLocation()); |
| } |
| return value; |
| } |
| |
| /** |
| * Set the widget by the id attribute |
| */ |
| protected void setWidget(String loc, Attributes attrs) throws SAXException { |
| setWidget(loc, getRequiredAttributeValue(loc, attrs, "id")); |
| } |
| |
| /** |
| * Set the widget by its path |
| */ |
| protected void setWidget(String loc, String path) throws SAXException { |
| widget = contextWidget.lookupWidget(path); |
| if (widget == null) { |
| if (contextWidget.getRequestParameterName().length() == 0) { |
| throw new SAXException("Element '" + loc + "' refers to unexistent widget path '" + path + "', " + |
| "relative to the form container, at " + getLocation()); |
| } else { |
| throw new SAXException("Element '" + loc + "' refers to unexistent widget path '" + path + "', " + |
| "relative to the '" + contextWidget.getRequestParameterName() + "', " + |
| "at " + getLocation()); |
| } |
| } |
| } |
| |
| /** |
| * Set typed widget by the id attribute |
| */ |
| protected void setTypedWidget(String loc, Attributes attrs, Class wclass, String wname) throws SAXException { |
| setWidget(loc, attrs); |
| if (!wclass.isInstance(widget)) { |
| throw new SAXException("Element '" + loc + "' can only be used with " + wname + " widgets, " + |
| "at " + getLocation()); |
| } |
| } |
| |
| protected boolean isVisible(Widget widget) { |
| return widget.getCombinedState().isDisplayingValues(); |
| } |
| |
| /** |
| * Needed to get things working with JDK 1.3. Can be removed once we |
| * don't support that platform any more. |
| */ |
| protected ContentHandler getContentHandler() { |
| return this.contentHandler; |
| } |
| |
| /** |
| * Needed to get things working with JDK 1.3. Can be removed once we |
| * don't support that platform any more. |
| */ |
| protected LexicalHandler getLexicalHandler() { |
| return this.lexicalHandler; |
| } |
| |
| /** |
| * Process the SAX event. |
| * @see org.xml.sax.ContentHandler#startPrefixMapping |
| */ |
| public void startPrefixMapping(String prefix, String uri) |
| throws SAXException { |
| if (prefix != null) { |
| this.namespaces.add(new String[] {prefix, uri}); |
| } |
| |
| // Consume template namespace mapping |
| if (!FormsConstants.TEMPLATE_NS.equals(uri)) { |
| super.startPrefixMapping(prefix, uri); |
| } |
| } |
| |
| /** |
| * Process the SAX event. |
| * @see org.xml.sax.ContentHandler#endPrefixMapping |
| */ |
| public void endPrefixMapping(String prefix) |
| throws SAXException { |
| String uri = null; |
| |
| if (prefix != null) { |
| // Find and remove the namespace prefix |
| boolean found = false; |
| for (int i = this.namespaces.size() - 1; i >= 0; i--) { |
| final String[] prefixAndUri = (String[]) this.namespaces.get(i); |
| if (prefixAndUri[0].equals(prefix)) { |
| uri = prefixAndUri[1]; |
| this.namespaces.remove(i); |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| throw new SAXException("Namespace for prefix '" + prefix + "' not found."); |
| } |
| } |
| |
| // Consume template namespace mapping |
| if (!FormsConstants.TEMPLATE_NS.equals(uri)) { |
| super.endPrefixMapping(prefix); |
| } |
| } |
| |
| /** |
| * @return True if prefix is already mapped into the namespace |
| */ |
| protected boolean hasPrefixMapping(String uri, String prefix) { |
| final int l = this.namespaces.size(); |
| for (int i = 0; i < l; i++) { |
| String[] prefixAndUri = (String[]) this.namespaces.get(i); |
| if (prefixAndUri[0].equals(prefix) && prefixAndUri[1].equals(uri)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| // |
| // Handler classes to transform CForms template elements |
| // |
| |
| protected class NestedHandler extends CopyHandler { |
| public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| // Is it forms namespace? |
| if (!FormsConstants.TEMPLATE_NS.equals(uri)) { |
| return hNested; |
| } |
| |
| Handler handler = (Handler) templates.get(loc); |
| if (handler == null) { |
| throw new SAXException("Element '" + loc + "' was not recognized, " + |
| "at " + getLocation()); |
| } |
| |
| return handler; |
| } |
| } |
| |
| /** |
| * Top level handler for the forms template |
| */ |
| protected class DocHandler extends CopyHandler { |
| public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| if (FormsConstants.TEMPLATE_NS.equals(uri)) { |
| if (!FORM_TEMPLATE_EL.equals(loc)) { |
| throw new SAXException("Element '" + loc + "' is not permitted outside of " + |
| "'form-template', at " + getLocation()); |
| } |
| return hForm; |
| } |
| |
| return super.nestedElement(uri, loc, raw, attrs); |
| } |
| } |
| |
| /** |
| * <code>ft:form-template</code> element handler. |
| * <pre> |
| * <ft:form-template locale="..." location="..."> |
| * ... |
| * </ft:form-template> |
| * </pre> |
| */ |
| protected class FormHandler extends NestedHandler { |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| if (contextWidget != null) { |
| throw new SAXException("Element 'form-template' can not be nested, " + |
| "at " + getLocation()); |
| } |
| |
| AttributesImpl newAttrs = attrs == null || attrs.getLength() == 0? |
| new AttributesImpl(): |
| new AttributesImpl(attrs); |
| |
| // ====> Retrieve the form |
| String formLocation = attrs.getValue(LOCATION); |
| if (formLocation != null) { |
| // Remove the location attribute |
| newAttrs.removeAttribute(newAttrs.getIndex(LOCATION)); |
| } |
| contextWidget = pipeContext.findForm(formLocation); |
| |
| // ====> Check if form visible (and skip it if it's not) |
| if (!isVisible(contextWidget)) { |
| return hNull; |
| } |
| |
| // set some general attributes |
| // top-level widget-containers like forms might have their id set to "" |
| // for those the @id should not be included. |
| if (contextWidget.getId().length() != 0 && newAttrs.getValue("id") == null ) { |
| newAttrs.addCDATAAttribute("id", contextWidget.getRequestParameterName()); |
| } |
| |
| // Add the "state" attribute |
| if ( newAttrs.getValue("state") == null ) { |
| newAttrs.addCDATAAttribute("state", contextWidget.getCombinedState().getName()); |
| } |
| |
| // Add the "listening" attribute is the value has change listeners |
| if (contextWidget instanceof ValueChangedListenerEnabled && |
| ((ValueChangedListenerEnabled)contextWidget).hasValueChangedListeners() && |
| newAttrs.getValue("listening") == null ) { |
| newAttrs.addCDATAAttribute("listening", "true"); |
| } |
| |
| // ====> Determine the Locale |
| // TODO pull this locale stuff also up in the Config object? |
| String localeAttr = attrs.getValue("locale"); |
| if (localeAttr != null) { // first use value of locale attribute if any |
| localeAttr = pipeContext.translateText(localeAttr); |
| pipeContext.setLocale(I18nUtils.parseLocale(localeAttr)); |
| } else if (pipeContext.getLocaleParameter() != null) { // then use locale specified as transformer parameter, if any |
| pipeContext.setLocale(pipeContext.getLocaleParameter()); |
| } else { |
| // use locale specified in bizdata supplied for form |
| Object locale = null; |
| try { |
| locale = pipeContext.evaluateExpression("/locale"); |
| } catch (JXPathException e) {} |
| if (locale != null) { |
| pipeContext.setLocale((Locale)locale); |
| } else { |
| // final solution: use locale defined in the server machine |
| pipeContext.setLocale(Locale.getDefault()); |
| } |
| } |
| |
| // We need to merge input.attrs with possible overruling attributes |
| // from the pipeContext |
| pipeContext.addFormAttributes(newAttrs); |
| String[] namesToTranslate = {"action"}; |
| Attributes transAttrs = null; |
| try { |
| transAttrs = translateAttributes(newAttrs, namesToTranslate); |
| } catch (RuntimeException e) { |
| throw new SAXException( e.getMessage() + " " +getLocation()); |
| } |
| |
| hasInstanceNamespace = hasPrefixMapping(FormsConstants.INSTANCE_NS, FormsConstants.INSTANCE_PREFIX); |
| if (!hasInstanceNamespace) { |
| getContentHandler().startPrefixMapping(FormsConstants.INSTANCE_PREFIX, FormsConstants.INSTANCE_NS); |
| } |
| getContentHandler().startElement(FormsConstants.INSTANCE_NS, "form-template", FormsConstants.INSTANCE_PREFIX_COLON + "form-template", transAttrs); |
| return this; |
| } |
| |
| public void endElement(String uri, String loc, String raw) |
| throws SAXException { |
| getContentHandler().endElement(FormsConstants.INSTANCE_NS, "form-template", FormsConstants.INSTANCE_PREFIX_COLON + "form-template"); |
| if (!hasInstanceNamespace) { |
| getContentHandler().endPrefixMapping(FormsConstants.INSTANCE_PREFIX); |
| } |
| contextWidget = null; |
| } |
| } |
| |
| /** |
| * <code>ft:choose</code>, <code>ft:union</code> use this. |
| */ |
| protected class SkipHandler extends NestedHandler { |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| return this; |
| } |
| |
| public void endElement(String uri, String loc, String raw) |
| throws SAXException { |
| } |
| } |
| |
| // |
| // Widget Handlers |
| // |
| |
| /** |
| * Handles <code>ft:widget-label</code> element. |
| */ |
| protected class WidgetLabelHandler extends ErrorHandler { |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| setWidget(loc, attrs); |
| widget.generateLabel(getContentHandler()); |
| return this; |
| } |
| |
| public void endElement(String uri, String loc, String raw) |
| throws SAXException { |
| } |
| } |
| |
| /** |
| * Handles <code>ft:widget</code> element. |
| */ |
| protected class WidgetHandler extends NullHandler { |
| // Widgets can't be nested, so this variable is Ok |
| private boolean hasStyling; |
| |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| setWidget(loc, attrs); |
| if (!isVisible(widget)) { |
| return hNull; |
| } |
| |
| hasStyling = false; |
| return this; |
| } |
| |
| public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| if (FormsConstants.INSTANCE_NS.equals(uri)) { |
| if (!STYLING_EL.equals(loc)) { |
| throw new SAXException("Element '" + loc + "' is not permitted within 'widget', " + |
| "at " + getLocation()); |
| } |
| hasStyling = true; |
| beginBuffer(); |
| // Buffer styling elements |
| return hBuffer; |
| } |
| return hNull; |
| } |
| |
| public void endElement(String uri, String loc, String raw) |
| throws SAXException { |
| if (hasStyling) { |
| // Pipe widget XML through the special handler to insert styling element |
| // before fi:widget end element. |
| hasStyling = false; |
| hStyling.recycle(); |
| hStyling.setSaxFragment(endBuffer()); |
| hStyling.setContentHandler(getContentHandler()); |
| hStyling.setLexicalHandler(getLexicalHandler()); |
| widget.generateSaxFragment(hStyling, pipeContext.getLocale()); |
| } else { |
| // Pipe widget XML directly into the output handler |
| widget.generateSaxFragment(getContentHandler(), pipeContext.getLocale()); |
| } |
| widget = null; |
| } |
| } |
| |
| // |
| // Repeater Handlers |
| // |
| |
| /** |
| * Handles <code>ft:repeater-size</code> element. |
| */ |
| protected class RepeaterSizeHandler extends ErrorHandler { |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| setTypedWidget(loc, attrs, Repeater.class, "repeater"); |
| ((Repeater) widget).generateSize(getContentHandler()); |
| widget = null; |
| return this; |
| } |
| |
| public void endElement(String uri, String loc, String raw) |
| throws SAXException { |
| } |
| } |
| |
| /** |
| * Handles <code>ft:repeater-widget-label</code> element. |
| */ |
| protected class RepeaterWidgetLabelHandler extends ErrorHandler { |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| Repeater repeater; |
| if (contextWidget instanceof Repeater) { |
| repeater = (Repeater)contextWidget; |
| } else { |
| setTypedWidget(loc, attrs, Repeater.class, "repeater"); |
| repeater = (Repeater)widget; |
| widget = null; |
| } |
| String path = getRequiredAttributeValue(loc, attrs, "widget-id"); |
| repeater.generateWidgetLabel(path, getContentHandler()); |
| return this; |
| } |
| |
| public void endElement(String uri, String loc, String raw) |
| throws SAXException { |
| } |
| } |
| |
| /** |
| * Handles <code>ft:repeater</code> element. Should contain repeater-rows |
| */ |
| protected class RepeaterHandler extends NestedHandler { |
| protected Class getWidgetClass() { |
| return Repeater.class; |
| } |
| |
| protected String getWidgetName() { |
| return "repeater"; |
| } |
| |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| setTypedWidget(loc, attrs, getWidgetClass(), getWidgetName()); |
| if (!isVisible(widget)) { |
| return hNull; |
| } |
| |
| contextWidgets.addFirst(contextWidget); |
| contextWidget = widget; |
| return this; |
| } |
| |
| public void endElement(String uri, String loc, String raw) |
| throws SAXException { |
| contextWidget = (Widget) contextWidgets.removeFirst(); |
| } |
| } |
| |
| /** |
| * Handles <code>ft:repeater-rows</code> element. |
| */ |
| protected class RepeaterRowsHandler extends BufferHandler { |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| if (!(contextWidget instanceof Repeater)) { |
| throw new SAXException("<repeater-rows> cannot be used with " + contextWidget + ", at " + getLocation()); |
| } |
| beginBuffer(); |
| return this; |
| } |
| |
| public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| return hBuffer; |
| } |
| |
| public void endElement(String uri, String loc, String raw) |
| throws SAXException { |
| SaxBuffer buffer = endBuffer(); |
| final Repeater repeater = (Repeater) contextWidget; |
| final int rowCount = repeater.getSize(); |
| pushHandler(hNested); |
| contextWidgets.addFirst(contextWidget); |
| for (int i = 0; i < rowCount; i++) { |
| contextWidget = repeater.getRow(i); |
| if (isVisible(contextWidget)) { |
| buffer.toSAX(EffectWidgetReplacingPipe.this); |
| } |
| } |
| contextWidget = (Widget) contextWidgets.removeFirst(); |
| popHandler(); |
| widget = null; |
| } |
| } |
| |
| /** |
| * Handles <code>ft:repeater-widget</code> element: a single element for both the repeater and its rows |
| */ |
| protected class RepeaterWidgetHandler extends BufferHandler { |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| setTypedWidget(loc, attrs, Repeater.class, "repeater"); |
| if (isVisible(widget)) { |
| beginBuffer(); |
| return this; |
| } |
| return hNull; |
| } |
| |
| public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| return hBuffer; |
| } |
| |
| public void endElement(String uri, String loc, String raw) |
| throws SAXException { |
| SaxBuffer buffer = endBuffer(); |
| final Repeater repeater = (Repeater) widget; |
| final int rowCount = repeater.getSize(); |
| pushHandler(hNested); |
| contextWidgets.addFirst(contextWidget); |
| for (int i = 0; i < rowCount; i++) { |
| contextWidget = repeater.getRow(i); |
| if (isVisible(contextWidget)) { |
| buffer.toSAX(EffectWidgetReplacingPipe.this); |
| } |
| } |
| contextWidget = (Widget) contextWidgets.removeFirst(); |
| popHandler(); |
| widget = null; |
| } |
| } |
| |
| // |
| // Grouping widgets Handlers |
| // |
| |
| /** |
| * Handles <code>ft:group</code> element. |
| */ |
| protected class GroupHandler extends NestedHandler { |
| protected Class getWidgetClass() { |
| return Group.class; |
| } |
| |
| protected String getWidgetName() { |
| return "group"; |
| } |
| |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| setTypedWidget(loc, attrs, getWidgetClass(), getWidgetName()); |
| if (!isVisible(widget)) { |
| return hNull; |
| } |
| |
| contextWidgets.addFirst(contextWidget); |
| contextWidget = widget; |
| return this; |
| } |
| |
| public void endElement(String uri, String loc, String raw) |
| throws SAXException { |
| contextWidget = (Widget) contextWidgets.removeFirst(); |
| } |
| } |
| |
| /** |
| * Handles <code>ft:aggregate</code> element. |
| */ |
| protected class AggregateWidgetHandler extends GroupHandler { |
| protected Class getWidgetClass() { |
| return AggregateField.class; |
| } |
| |
| protected String getWidgetName() { |
| return "aggregate"; |
| } |
| } |
| |
| /** |
| * Handles <code>ft:choose</code> element. |
| */ |
| protected class ChooseHandler extends CopyHandler { |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException { |
| setWidget(loc, getRequiredAttributeValue(loc, attrs, "path")); |
| // TODO: Should instead check for datatype convertable to String. |
| if (!(widget instanceof DataWidget)) { |
| throw new SAXException("Element '" + loc + "' can only be used with DataWidget widgets, " + |
| "at " + getLocation()); |
| } |
| // Choose does not change the context widget like Union does. |
| chooseWidgets.addFirst(widget); |
| return this; |
| } |
| |
| public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) throws SAXException { |
| if (FormsConstants.TEMPLATE_NS.equals(uri)) { |
| if ("when".equals(loc)) { |
| String testValue = getAttributeValue(loc, attrs, "value"); |
| String value = (String) ((Widget) chooseWidgets.get(0)).getValue(); |
| return testValue.equals(value) ? hSkip : hNull; |
| } |
| throw new SAXException("Element '" + loc + "' is not permitted within 'choose', " + |
| "at " + getLocation()); |
| } |
| return hChoosePassThru; |
| } |
| |
| public void endElement(String uri, String loc, String raw) throws SAXException { |
| chooseWidgets.removeFirst(); |
| } |
| } |
| |
| /** |
| * Handles <code>ft:choose/ft:when</code> element. |
| */ |
| protected class ChoosePassThruHandler extends CopyHandler { |
| public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) throws SAXException { |
| if (FormsConstants.TEMPLATE_NS.equals(uri)) { |
| if ("when".equals(loc)) { |
| String testValue = getAttributeValue(loc, attrs, "value"); |
| String value = (String) ((Widget) chooseWidgets.get(0)).getValue(); |
| return testValue.equals(value)? hSkip: hNull; |
| } |
| throw new SAXException("Element '" + loc + "' is not permitted within 'choose', " + |
| "at " + getLocation()); |
| } |
| return this; |
| } |
| } |
| |
| /** |
| * Handles <code>ft:struct</code> element. |
| */ |
| protected class StructHandler extends GroupHandler { |
| protected Class getWidgetClass() { |
| return Struct.class; |
| } |
| |
| protected String getWidgetName() { |
| return "struct"; |
| } |
| } |
| |
| /** |
| * Handles <code>ft:union</code> element. |
| */ |
| protected class UnionHandler extends GroupHandler { |
| protected Class getWidgetClass() { |
| return Union.class; |
| } |
| |
| protected String getWidgetName() { |
| return "union"; |
| } |
| |
| public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| if (FormsConstants.TEMPLATE_NS.equals(uri)) { |
| if ("case".equals(loc)) { |
| String id = getAttributeValue(loc, attrs, "id"); |
| String value = (String) contextWidget.getValue(); |
| if (id.equals(value != null ? value : "")) { |
| return hSkip; |
| } |
| return hNull; |
| } |
| throw new SAXException("Element '" + loc + "' is not permitted within 'union', " + |
| "at " + getLocation()); |
| } |
| return hUnionPassThru; |
| } |
| } |
| |
| /** |
| * Handles <code>ft:union/ft:case</code> element. |
| */ |
| protected class UnionPassThruHandler extends CopyHandler { |
| public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| if (FormsConstants.TEMPLATE_NS.equals(uri)) { |
| if ("case".equals(loc)) { |
| if (contextWidget.getValue().equals(attrs.getValue("id"))) { |
| return hSkip; |
| } |
| return hNull; |
| } |
| throw new SAXException("Element '" + loc + "' is not permitted within 'union', " + |
| "at " + getLocation()); |
| } |
| return this; |
| } |
| } |
| |
| /** |
| * Handles <code>ft:new</code> element. |
| */ |
| protected class NewHandler extends CopyHandler { |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| String id = getRequiredAttributeValue(loc, attrs, "id"); |
| SaxBuffer buffer = (SaxBuffer) classes.get(id); |
| if (buffer == null) { |
| throw new SAXException("New: Class '" + id + "' does not exist, " + |
| "at " + getLocation()); |
| } |
| pushHandler(hNested); |
| buffer.toSAX(EffectWidgetReplacingPipe.this); |
| popHandler(); |
| return this; |
| } |
| |
| public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| return hNull; |
| } |
| |
| public void endElement(String uri, String loc, String raw) |
| throws SAXException { |
| } |
| } |
| |
| /** |
| * Handles <code>ft:class</code> element. |
| * <pre> |
| * <ft:class id="..."> |
| * ... |
| * </ft:class> |
| * </pre> |
| */ |
| protected class ClassHandler extends BufferHandler { |
| // FIXME What if <class> is nested within <class>? |
| private String widgetPath; |
| |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| widgetPath = getRequiredAttributeValue(loc, attrs, "id"); |
| beginBuffer(); |
| return this; |
| } |
| |
| public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| return hBuffer; |
| } |
| |
| public void endElement(String uri, String loc, String raw) throws SAXException { |
| classes.put(widgetPath, endBuffer()); |
| } |
| } |
| |
| /** |
| * Handles <code>ft:continuation-id</code> element. |
| * <pre> |
| * <ft:continuation-id/> |
| * </pre> |
| */ |
| protected class ContinuationIdHandler extends ErrorHandler { |
| protected String getName() { |
| return "continuation-id"; |
| } |
| |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| // Insert the continuation id |
| // FIXME(SW) we could avoid costly JXPath evaluation if we had the objectmodel here. |
| Object idObj = pipeContext.evaluateExpression("$cocoon/continuation/id"); |
| if (idObj == null) { |
| throw new SAXException("No continuation found"); |
| } |
| |
| String id = idObj.toString(); |
| getContentHandler().startElement(FormsConstants.INSTANCE_NS, "continuation-id", FormsConstants.INSTANCE_PREFIX_COLON + "continuation-id", attrs); |
| getContentHandler().characters(id.toCharArray(), 0, id.length()); |
| getContentHandler().endElement(FormsConstants.INSTANCE_NS, "continuation-id", FormsConstants.INSTANCE_PREFIX_COLON + "continuation-id"); |
| return this; |
| } |
| |
| public void endElement(String uri, String loc, String raw) throws SAXException { |
| } |
| } |
| |
| /** |
| * This ContentHandler helps in inserting SAX events before the closing tag of the root |
| * element. |
| */ |
| protected class StylingContentHandler extends AbstractXMLPipe { |
| |
| private int elementNesting; |
| private SaxBuffer styling; |
| |
| public void setSaxFragment(SaxBuffer saxFragment) { |
| styling = saxFragment; |
| } |
| |
| public void recycle() { |
| super.recycle(); |
| elementNesting = 0; |
| styling = null; |
| } |
| |
| public void startElement(String uri, String loc, String raw, Attributes a) |
| throws SAXException { |
| elementNesting++; |
| super.startElement(uri, loc, raw, a); |
| } |
| |
| public void endElement(String uri, String loc, String raw) |
| throws SAXException { |
| elementNesting--; |
| if (elementNesting == 0) { |
| styling.toSAX(getContentHandler()); |
| } |
| super.endElement(uri, loc, raw); |
| } |
| } |
| |
| /** |
| * Inserts validation errors (if any) for the Field widgets |
| */ |
| protected class ValidationErrorHandler extends NullHandler { |
| public Handler startElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| setWidget(loc, attrs); |
| return this; |
| } |
| |
| public Handler nestedElement(String uri, String loc, String raw, Attributes attrs) |
| throws SAXException { |
| return hNull; |
| } |
| |
| public void endElement(String uri, String loc, String raw) |
| throws SAXException { |
| if (widget instanceof ValidationErrorAware) { |
| ValidationError error = ((ValidationErrorAware)widget).getValidationError(); |
| if (error != null) { |
| getContentHandler().startElement(FormsConstants.INSTANCE_NS, VALIDATION_ERROR, FormsConstants.INSTANCE_PREFIX_COLON + VALIDATION_ERROR, XMLUtils.EMPTY_ATTRIBUTES); |
| error.generateSaxFragment(getContentHandler()); |
| getContentHandler().endElement(FormsConstants.INSTANCE_NS, VALIDATION_ERROR, FormsConstants.INSTANCE_PREFIX_COLON + VALIDATION_ERROR); |
| } |
| } |
| widget = null; |
| } |
| } |
| |
| |
| |
| private Attributes translateAttributes(Attributes attributes, String[] names) { |
| AttributesImpl newAtts = new AttributesImpl(attributes); |
| if (names!= null) { |
| for (int i = 0; i < names.length; i++) { |
| String name = names[i]; |
| int position = newAtts.getIndex(name); |
| String newValue = pipeContext.translateText(newAtts.getValue(position)); |
| if(position>-1) |
| newAtts.setValue(position, newValue); |
| else |
| throw new RuntimeException("Attribute \""+name+"\" not present!"); |
| } |
| } |
| return newAtts; |
| } |
| } |