| /* |
| * 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; |
| } |
| } |
| } |