blob: bf98ae0613544f293a6bcb611710b5a300596679 [file] [log] [blame]
/*
* 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.components.language.markup.xsp;
import org.apache.cocoon.components.language.markup.AbstractMarkupLanguage;
import org.apache.cocoon.components.language.markup.LogicsheetFilter;
import org.apache.cocoon.xml.AbstractXMLPipe;
import org.apache.cocoon.xml.AttributesImpl;
import org.apache.cocoon.xml.XMLConsumer;
import org.apache.cocoon.xml.XMLUtils;
import org.apache.commons.lang.StringUtils;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.ext.LexicalHandler;
import java.util.LinkedList;
/**
* Filter attributes and text and expand {#expr} to xsp:attribute and xsp:expr
* elements.
*
* @version $Id$
*/
public class XSPExpressionFilter extends LogicsheetFilter
implements XSPExpressionParser.Handler {
public static class XMLPipeAdapter extends AbstractXMLPipe {
private XSPExpressionFilter expressionFilter;
private AbstractXMLPipe additionalFilter;
public XMLPipeAdapter(XSPExpressionFilter expressionFilter, AbstractXMLPipe additionalFilter) {
this.additionalFilter = additionalFilter;
this.expressionFilter = expressionFilter;
super.setLexicalHandler(additionalFilter);
super.setContentHandler(expressionFilter);
expressionFilter.setContentHandler(additionalFilter);
}
public void setConsumer(XMLConsumer consumer) {
additionalFilter.setConsumer(consumer);
}
public void setContentHandler(ContentHandler handler) {
additionalFilter.setContentHandler(handler);
}
public void setLexicalHandler(LexicalHandler handler) {
additionalFilter.setLexicalHandler(handler);
}
public void setDocumentLocator(Locator locator) {
additionalFilter.setDocumentLocator(locator);
expressionFilter.setDocumentLocator(locator);
}
}
/** The markup language URI */
private String markupURI;
/** The markup language prefix */
private String markupPrefix;
/** Interpolation settings as nested properties */
private LinkedList interpolationStack = new LinkedList();
/** Default interpolation settings for given markup language */
private InterpolationSettings defaultInterpolationSettings;
/** The parser for XSP value templates */
private XSPExpressionParser expressionParser = new XSPExpressionParser(this);
public XSPExpressionFilter(XSPMarkupLanguage markup) {
this.markupURI = markup.getURI();
this.markupPrefix = markup.getPrefix();
// Initialize default interpolation settings.
defaultInterpolationSettings
= new InterpolationSettings(markup.hasAttrInterpolation(),
markup.hasTextInterpolation());
}
/**
* Create a new <code>{@link XSPExpressionFilter}</code>.
*/
public void startDocument() throws SAXException {
interpolationStack.clear();
interpolationStack.addLast(defaultInterpolationSettings);
super.startDocument();
}
/**
* Start a new element. If attribute value templates are enabled and the element has attributes
* with templates, these are replaced by xsp:attribute tags.
*
* @see org.xml.sax.ContentHandler#startElement(java.lang.String, java.lang.String,
* java.lang.String, org.xml.sax.Attributes)
*/
public void startElement(String namespaceURI, String localName, String qName, Attributes attribs)
throws SAXException {
expressionParser.flush(locator, "...<"+qName+">");
// Check template for interpolation flags
attribs = pushInterpolationStack(attribs);
if (getInterpolationSettings().attrInterpolation) {
// Attribute value templates enabled => process attributes
AttributesImpl staticAttribs = new AttributesImpl();
AttributesImpl dynamicAttribs = new AttributesImpl();
// Gather attributes with and without templates separately
for (int i = 0; i < attribs.getLength(); ++i) {
String value = attribs.getValue(i);
if (value.indexOf("{#") != -1) {
// The attribute contains templates
dynamicAttribs.addAttribute(attribs.getURI(i), attribs.getLocalName(i), attribs.getQName(i),
attribs.getType(i), value);
}
else {
// The attribute does not contain templates
staticAttribs.addAttribute(attribs.getURI(i), attribs.getLocalName(i), attribs.getQName(i),
attribs.getType(i), value);
}
}
// Start the element with template-free attributes
super.startElement(namespaceURI, localName, qName, staticAttribs);
// Generate xsp:attribute elements for the attributes containing templates
for (int i = 0; i < dynamicAttribs.getLength(); ++i) {
AttributesImpl elemAttribs = new AttributesImpl();
addAttribute(elemAttribs, "uri", dynamicAttribs.getURI(i));
String qname = dynamicAttribs.getQName(i);
if (qname != null) {
addAttribute(elemAttribs, "prefix", StringUtils.left(qname, qname.indexOf(':')));
}
String attrName = dynamicAttribs.getLocalName(i);
addAttribute(elemAttribs, "name", attrName);
super.startElement(markupURI, "attribute", markupPrefix + ":attribute", elemAttribs);
expressionParser.consume(dynamicAttribs.getValue(i));
expressionParser.flush(locator, "<"+qName+" "+attrName+"=\"...\">");
super.endElement(markupURI, "attribute", markupPrefix + ":attribute");
}
} else {
// Attribute value templates disabled => pass through element
super.startElement(namespaceURI, localName, qName, attribs);
}
}
/**
* Check attributes for presence of interpolation flags.
* Push current settings to stack.
* Remove interpolation attributes and return cleaned attribute list.
*/
private Attributes pushInterpolationStack(Attributes attribs) {
String valueAttr = attribs.getValue(markupURI, AbstractMarkupLanguage.ATTR_INTERPOLATION);
String valueText = attribs.getValue(markupURI, AbstractMarkupLanguage.TEXT_INTERPOLATION);
// Neither interpolation flag in attribute list: push tail to stack.
if (valueAttr == null && valueText == null ) {
interpolationStack.addLast(interpolationStack.getLast());
return attribs;
}
// Push new interpolation settings to stack and remove attributes.
InterpolationSettings lastSettings = (InterpolationSettings)interpolationStack.getLast();
boolean attrInterpolation = lastSettings.attrInterpolation;
boolean textInterpolation = lastSettings.textInterpolation;
AttributesImpl cleanedAttribs = new AttributesImpl(attribs);
if (valueAttr != null) {
attrInterpolation = Boolean.valueOf(valueAttr).booleanValue();
cleanedAttribs.removeAttribute(cleanedAttribs.getIndex(markupURI, AbstractMarkupLanguage.ATTR_INTERPOLATION));
}
if (valueText != null) {
textInterpolation = Boolean.valueOf(valueText).booleanValue();
cleanedAttribs.removeAttribute(cleanedAttribs.getIndex(markupURI, AbstractMarkupLanguage.TEXT_INTERPOLATION));
}
interpolationStack.addLast(new InterpolationSettings(attrInterpolation,
textInterpolation));
return cleanedAttribs;
}
/**
* Flush the current expression.
*/
public void endElement(String uri, String loc, String raw) throws SAXException {
expressionParser.flush(locator, "...</"+raw+">");
super.endElement(uri, loc, raw);
// Pop stack of interpolation settings.
interpolationStack.removeLast();
}
/**
* Handle characters. If text templates are enabled, the text is parsed and expressions are
* replaced.
*
* @see org.xml.sax.ContentHandler#characters(char[], int, int)
*/
public void characters(char[] ch, int start, int length) throws SAXException {
if (getInterpolationSettings().textInterpolation) {
// Text templated enabled => Replace text expressions
expressionParser.consume(ch, start, length);
}
else {
// Text templates disabled => pass through text
super.characters(ch, start, length);
}
}
/**
* Forward text to parent class.
*
* @see org.apache.cocoon.components.language.markup.xsp.XSPExpressionParser.Handler#handleText(char[],
* int, int)
*/
public void handleText(char[] chars, int start, int length) throws SAXException {
super.characters(chars, start, length);
}
/**
* Wrap expressions in xsp:expr tags.
*
* @see org.apache.cocoon.components.language.markup.xsp.XSPExpressionParser.Handler#handleExpression(char[],
* int, int)
*/
public void handleExpression(char[] chars, int start, int length) throws SAXException {
super.startElement(markupURI, "expr", markupPrefix + ":expr", XMLUtils.EMPTY_ATTRIBUTES);
super.characters(chars, start, length);
super.endElement(markupURI, "expr", markupPrefix + ":expr");
}
/**
* Add an attribute if it is neither <code>null</code> nor empty (length 0).
*
* @param attribs The attributes
* @param name The attribute name
* @param value The attribute value
*/
protected void addAttribute(AttributesImpl attribs, String name, String value) {
if (value != null && value.length() > 0) {
attribs.addCDATAAttribute(name, value);
}
}
/**
* Return current interpolation settings.
*/
private InterpolationSettings getInterpolationSettings() {
return (InterpolationSettings)interpolationStack.getLast();
}
/**
* Structure to hold settings for attribute and text interpolation.
*/
private static class InterpolationSettings {
boolean attrInterpolation;
boolean textInterpolation;
InterpolationSettings(boolean attrInterpolation, boolean textInterpolation) {
this.attrInterpolation = attrInterpolation;
this.textInterpolation = textInterpolation;
}
}
}