001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.woody.transformation;
018:
019: import java.util.Locale;
020:
021: import org.apache.avalon.excalibur.pool.Recyclable;
022: import org.apache.cocoon.i18n.I18nUtils;
023: import org.apache.cocoon.woody.Constants;
024: import org.apache.cocoon.woody.formmodel.Repeater;
025: import org.apache.cocoon.woody.formmodel.Widget;
026: import org.apache.cocoon.xml.AbstractXMLPipe;
027: import org.apache.cocoon.xml.SaxBuffer;
028: import org.apache.commons.jxpath.JXPathException;
029: import org.xml.sax.Attributes;
030: import org.xml.sax.SAXException;
031: import org.xml.sax.helpers.AttributesImpl;
032:
033: /**
034: * The basic operation of this Pipe is that it replaces wt:widget (in the
035: * {@link Constants#WT_NS} namespace) tags (having an id attribute)
036: * by the XML representation of the corresponding widget instance.
037: *
038: * <p>These XML fragments (normally all in the {@link Constants#WI_NS "Woody Instance"} namespace), can
039: * then be translated to a HTML presentation by an XSL. This XSL will then only have to style
040: * individual widget, and will not need to do the whole page layout.
041: *
042: * <p>For more information about the supported tags and their function, see the user documentation
043: * for the woody template transformer.</p>
044: *
045: * @version CVS $Id: WidgetReplacingPipe.java 433543 2006-08-22 06:22:54Z crossley $
046: */
047: public class WidgetReplacingPipe extends AbstractXMLPipe {
048:
049: private static final String REPEATER_SIZE = "repeater-size";
050: private static final String REPEATER_WIDGET_LABEL = "repeater-widget-label";
051: private static final String WIDGET_LABEL = "widget-label";
052: private static final String WIDGET = "widget";
053: private static final String LOCATION = "location";
054: private static final String REPEATER_WIDGET = "repeater-widget";
055: private static final String CONTINUATION_ID = "continuation-id";
056: private static final String FORM_TEMPLATE_EL = "form-template";
057: private static final String STYLING_EL = "styling";
058:
059: protected Widget contextWidget;
060:
061: /**
062: * Indicates whether we're currently in a widget element.
063: */
064: protected boolean inWidgetElement;
065:
066: /**
067: * Buffer used to temporarily record SAX events.
068: */
069: protected SaxBuffer saxBuffer;
070:
071: /**
072: * Counts the element nesting.
073: */
074: protected int elementNestingCounter;
075:
076: /**
077: * Contains the value of the {@link #elementNestingCounter} on the moment the transformer
078: * encountered a wi:widget element. Used to detect the corresponding endElement call
079: * for the wi:widget element.
080: */
081: protected int widgetElementNesting;
082:
083: /**
084: * If {@link #inWidgetElement} = true, then this contains the widget currenlty being handled.
085: */
086: protected Widget widget;
087:
088: /**
089: * Boolean indicating wether the current widget requires special repeater-treatement.
090: */
091: protected boolean repeaterWidget;
092:
093: protected WidgetReplacingPipe.InsertStylingContentHandler stylingHandler = new WidgetReplacingPipe.InsertStylingContentHandler();
094: protected WoodyPipelineConfig pipeContext;
095:
096: /**
097: * Have we encountered a <wi:style> element in a widget ?
098: */
099: protected boolean gotStylingElement;
100:
101: /**
102: * Namespace prefix used for the namespace <code>Constants.WT_NS</code>.
103: */
104: protected String namespacePrefix;
105:
106: public void init(Widget newContextWidget,
107: WoodyPipelineConfig newPipeContext) {
108: contextWidget = newContextWidget;
109: inWidgetElement = false;
110: elementNestingCounter = 0;
111: pipeContext = newPipeContext;
112: }
113:
114: public void startElement(String namespaceURI, String localName,
115: String qName, Attributes attributes) throws SAXException {
116: elementNestingCounter++;
117:
118: if (inWidgetElement) {
119: if (elementNestingCounter == widgetElementNesting + 1
120: && Constants.WI_NS.equals(namespaceURI)
121: && STYLING_EL.equals(localName)) {
122: gotStylingElement = true;
123: }
124: saxBuffer.startElement(namespaceURI, localName, qName,
125: attributes);
126: } else if (Constants.WT_NS.equals(namespaceURI)) {
127: if (localName.equals(WIDGET)
128: || localName.equals(REPEATER_WIDGET)) {
129: checkContextWidgetAvailable(qName);
130: inWidgetElement = true;
131: widgetElementNesting = elementNestingCounter;
132: gotStylingElement = false;
133: saxBuffer = new SaxBuffer();
134: // retrieve widget here, but its XML will only be streamed in the endElement call
135: widget = getWidget(attributes);
136: repeaterWidget = localName.equals(REPEATER_WIDGET);
137: if (repeaterWidget && !(widget instanceof Repeater)) {
138: throw new SAXException(
139: "WoodyTemplateTransformer: the element \"repeater-widget\" can only be used for repeater widgets.");
140: }
141: } else if (localName.equals(WIDGET_LABEL)) {
142: checkContextWidgetAvailable(qName);
143: Widget widget = getWidget(attributes);
144: widget.generateLabel(contentHandler);
145: } else if (localName.equals(REPEATER_WIDGET_LABEL)) {
146: checkContextWidgetAvailable(qName);
147: Widget widget = getWidget(attributes);
148: if (!(widget instanceof Repeater)) {
149: throw new SAXException(
150: "WoodyTemplateTransformer: the element \"repeater-widget-label\" can only be used for repeater widgets.");
151: }
152: String widgetId = attributes.getValue("widget-id");
153: if (widgetId == null || widgetId.length() == 0) {
154: throw new SAXException(
155: "WoodyTemplateTransformer: the element \"repeater-widget-label\" requires a \"widget-id\" attribute.");
156: }
157: ((Repeater) widget).generateWidgetLabel(widgetId,
158: contentHandler);
159: } else if (localName.equals(REPEATER_SIZE)) {
160: checkContextWidgetAvailable(qName);
161: Widget widget = getWidget(attributes);
162: if (!(widget instanceof Repeater))
163: throw new SAXException(
164: "WoodyTemplateTransformer: the element \"repeater-size\" can only be used for repeater widgets.");
165: contentHandler.startPrefixMapping(Constants.WI_PREFIX,
166: Constants.WI_NS);
167: ((Repeater) widget).generateSize(contentHandler);
168: contentHandler.endPrefixMapping(Constants.WI_PREFIX);
169: } else if (localName.equals(FORM_TEMPLATE_EL)) {
170: if (contextWidget != null) {
171: throw new SAXException(
172: "Detected nested wt:form-template elements, this is not allowed.");
173: }
174: contentHandler.startPrefixMapping(Constants.WI_PREFIX,
175: Constants.WI_NS);
176:
177: // ====> Retrieve the form
178:
179: // first look for the form using the location attribute, if any
180: String formJXPath = attributes.getValue(LOCATION);
181: if (formJXPath != null) {
182: // remove the location attribute
183: AttributesImpl attrsCopy = new AttributesImpl(
184: attributes);
185: attrsCopy.removeAttribute(attributes
186: .getIndex(LOCATION));
187: attributes = attrsCopy;
188: }
189: contextWidget = pipeContext.findForm(formJXPath);
190:
191: // ====> Determine the Locale
192: //TODO pull this locale stuff also up in the Config object?
193:
194: String localeAttr = attributes.getValue("locale");
195: if (localeAttr != null) { // first use value of locale attribute if any
196: localeAttr = pipeContext.translateText(localeAttr);
197: pipeContext.setLocale(I18nUtils
198: .parseLocale(localeAttr));
199: } else if (pipeContext.getLocaleParameter() != null) { // then use locale specified as transformer parameter, if any
200: pipeContext.setLocale(pipeContext
201: .getLocaleParameter());
202: } else { // use locale specified in bizdata supplied for form
203: Object locale = null;
204: try {
205: locale = pipeContext
206: .evaluateExpression("/locale");
207: } catch (JXPathException e) {
208: }
209: if (locale != null) {
210: pipeContext.setLocale((Locale) locale);
211: } else {
212: // final solution: use locale defined in the server machine
213: pipeContext.setLocale(Locale.getDefault());
214: }
215: }
216:
217: String[] namesToTranslate = { "action" };
218: Attributes transAtts = translateAttributes(attributes,
219: namesToTranslate);
220: contentHandler.startElement(Constants.WI_NS,
221: FORM_TEMPLATE_EL, Constants.WI_PREFIX_COLON
222: + FORM_TEMPLATE_EL, transAtts);
223:
224: } else if (localName.equals(CONTINUATION_ID)) {
225: // Insert the continuation id
226: // FIXME(SW) we could avoid costly JXPath evaluation if we had the objectmodel here.
227: Object idObj = pipeContext
228: .evaluateExpression("$continuation/id");
229: if (idObj == null) {
230: throw new SAXException("No continuation found");
231: }
232:
233: String id = idObj.toString();
234: contentHandler.startPrefixMapping(Constants.WI_PREFIX,
235: Constants.WI_NS);
236: contentHandler.startElement(Constants.WI_NS,
237: CONTINUATION_ID, Constants.WI_PREFIX_COLON
238: + CONTINUATION_ID, attributes);
239: contentHandler.characters(id.toCharArray(), 0, id
240: .length());
241: contentHandler.endElement(Constants.WI_NS,
242: CONTINUATION_ID, Constants.WI_PREFIX_COLON
243: + CONTINUATION_ID);
244: contentHandler.endPrefixMapping(Constants.WI_PREFIX);
245: } else {
246: throw new SAXException(
247: "WoodyTemplateTransformer: Unsupported element: "
248: + localName);
249: }
250: } else {
251: super .startElement(namespaceURI, localName, qName,
252: attributes);
253: }
254: }
255:
256: private void checkContextWidgetAvailable(String widgetElementName)
257: throws SAXException {
258: if (contextWidget == null)
259: throw new SAXException(
260: widgetElementName
261: + " cannot be used outside a wt:form-template element");
262: }
263:
264: private Attributes translateAttributes(Attributes attributes,
265: String[] names) {
266: AttributesImpl newAtts = new AttributesImpl(attributes);
267: if (names != null) {
268: for (int i = 0; i < names.length; i++) {
269: String name = names[i];
270: int position = newAtts.getIndex(name);
271: String newValue = pipeContext.translateText(newAtts
272: .getValue(position));
273: newAtts.setValue(position, newValue);
274: }
275: }
276: return newAtts;
277: }
278:
279: protected Widget getWidget(Attributes attributes)
280: throws SAXException {
281: String widgetId = attributes.getValue("id");
282: if (widgetId == null || widgetId.length() == 0) {
283: throw new SAXException(
284: "WoodyTemplateTransformer: missing id attribute on a woody element.");
285: }
286: Widget widget = contextWidget.getWidget(widgetId);
287: if (widget == null) {
288: throw new SAXException(
289: "WoodyTemplateTransformer: widget with id \""
290: + widgetId
291: + "\" does not exist in the container "
292: + contextWidget.getFullyQualifiedId());
293: }
294: return widget;
295: }
296:
297: public void endElement(String namespaceURI, String localName,
298: String qName) throws SAXException {
299:
300: if (inWidgetElement) {
301: if (elementNestingCounter == widgetElementNesting
302: && Constants.WT_NS.equals(namespaceURI)
303: && (localName.equals(WIDGET) || localName
304: .equals(REPEATER_WIDGET))) {
305: if (repeaterWidget) {
306: Repeater repeater = (Repeater) widget;
307: WidgetReplacingPipe rowPipe = new WidgetReplacingPipe();
308: int rowCount = repeater.getSize();
309: for (int i = 0; i < rowCount; i++) {
310: Repeater.RepeaterRow row = repeater.getRow(i);
311: rowPipe.init(row, pipeContext);
312: rowPipe.setContentHandler(contentHandler);
313: rowPipe.setLexicalHandler(lexicalHandler);
314: saxBuffer.toSAX(rowPipe);
315: rowPipe.recycle();
316: }
317: } else {
318: stylingHandler.recycle();
319: stylingHandler.setSaxFragment(saxBuffer);
320: stylingHandler.setContentHandler(contentHandler);
321: stylingHandler.setLexicalHandler(lexicalHandler);
322: contentHandler.startPrefixMapping(
323: Constants.WI_PREFIX, Constants.WI_NS);
324: widget.generateSaxFragment(stylingHandler,
325: pipeContext.getLocale());
326: contentHandler
327: .endPrefixMapping(Constants.WI_PREFIX);
328: }
329: inWidgetElement = false;
330: widget = null;
331: } else {
332: saxBuffer.endElement(namespaceURI, localName, qName);
333: }
334: } else if (Constants.WT_NS.equals(namespaceURI)) {
335: if (localName.equals(WIDGET_LABEL)
336: || localName.equals(REPEATER_WIDGET_LABEL)
337: || localName.equals(REPEATER_SIZE)
338: || localName.equals(CONTINUATION_ID)) {
339: // Do nothing
340: } else if (localName.equals(FORM_TEMPLATE_EL)) {
341: contextWidget = null;
342: contentHandler.endElement(Constants.WI_NS,
343: FORM_TEMPLATE_EL, Constants.WI_PREFIX_COLON
344: + FORM_TEMPLATE_EL);
345: contentHandler.endPrefixMapping(Constants.WI_PREFIX);
346: } else {
347: super .endElement(namespaceURI, localName, qName);
348: }
349: } else {
350: super .endElement(namespaceURI, localName, qName);
351: }
352: elementNestingCounter--;
353: }
354:
355: public void startPrefixMapping(String prefix, String uri)
356: throws SAXException {
357: if (inWidgetElement) {
358: saxBuffer.startPrefixMapping(prefix, uri);
359: } else {
360: super .startPrefixMapping(prefix, uri);
361: }
362: }
363:
364: public void endPrefixMapping(String prefix) throws SAXException {
365: if (inWidgetElement) {
366: saxBuffer.endPrefixMapping(prefix);
367: } else {
368: super .endPrefixMapping(prefix);
369: }
370: }
371:
372: public void characters(char c[], int start, int len)
373: throws SAXException {
374: if (inWidgetElement) {
375: saxBuffer.characters(c, start, len);
376: } else {
377: super .characters(c, start, len);
378: }
379: }
380:
381: public void ignorableWhitespace(char c[], int start, int len)
382: throws SAXException {
383: if (inWidgetElement) {
384: saxBuffer.ignorableWhitespace(c, start, len);
385: } else {
386: super .ignorableWhitespace(c, start, len);
387: }
388: }
389:
390: public void processingInstruction(String target, String data)
391: throws SAXException {
392: if (inWidgetElement) {
393: saxBuffer.processingInstruction(target, data);
394: } else {
395: super .processingInstruction(target, data);
396: }
397: }
398:
399: public void skippedEntity(String name) throws SAXException {
400: if (inWidgetElement) {
401: saxBuffer.skippedEntity(name);
402: } else {
403: super .skippedEntity(name);
404: }
405: }
406:
407: public void startEntity(String name) throws SAXException {
408: if (inWidgetElement)
409: saxBuffer.startEntity(name);
410: else
411: super .startEntity(name);
412: }
413:
414: public void endEntity(String name) throws SAXException {
415: if (inWidgetElement) {
416: saxBuffer.endEntity(name);
417: } else {
418: super .endEntity(name);
419: }
420: }
421:
422: public void startCDATA() throws SAXException {
423: if (inWidgetElement) {
424: saxBuffer.startCDATA();
425: } else {
426: super .startCDATA();
427: }
428: }
429:
430: public void endCDATA() throws SAXException {
431: if (inWidgetElement) {
432: saxBuffer.endCDATA();
433: } else {
434: super .endCDATA();
435: }
436: }
437:
438: public void comment(char ch[], int start, int len)
439: throws SAXException {
440: if (inWidgetElement) {
441: saxBuffer.comment(ch, start, len);
442: } else {
443: super .comment(ch, start, len);
444: }
445: }
446:
447: public void recycle() {
448: super .recycle();
449: this .contextWidget = null;
450: this .widget = null;
451: this .namespacePrefix = null;
452: }
453:
454: /**
455: * This ContentHandler helps in inserting SAX events before the closing tag of the root
456: * element.
457: */
458: public class InsertStylingContentHandler extends AbstractXMLPipe
459: implements Recyclable {
460: private int elementNesting;
461: private SaxBuffer saxBuffer;
462:
463: public void setSaxFragment(SaxBuffer saxFragment) {
464: saxBuffer = saxFragment;
465: }
466:
467: public void recycle() {
468: super .recycle();
469: elementNesting = 0;
470: saxBuffer = null;
471: }
472:
473: public void startElement(String uri, String loc, String raw,
474: Attributes a) throws SAXException {
475: elementNesting++;
476: super .startElement(uri, loc, raw, a);
477: }
478:
479: public void endElement(String uri, String loc, String raw)
480: throws SAXException {
481: elementNesting--;
482: if (elementNesting == 0 && saxBuffer != null) {
483: if (gotStylingElement) {
484: // Just deserialize
485: saxBuffer.toSAX(contentHandler);
486: } else {
487: // Insert an enclosing <wi:styling>
488: contentHandler
489: .startElement(Constants.WI_NS, STYLING_EL,
490: Constants.WI_PREFIX_COLON
491: + STYLING_EL,
492: Constants.EMPTY_ATTRS);
493: saxBuffer.toSAX(contentHandler);
494: contentHandler.endElement(Constants.WI_NS,
495: STYLING_EL, Constants.WI_PREFIX_COLON
496: + STYLING_EL);
497: }
498: }
499: super.endElement(uri, loc, raw);
500: }
501: }
502: }
|