/* * 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.ajax; import java.io.IOException; import java.util.Map; import org.apache.avalon.framework.parameters.Parameters; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.Request; import org.apache.cocoon.environment.SourceResolver; import org.apache.cocoon.transformation.AbstractTransformer; import org.apache.cocoon.xml.AttributesImpl; import org.apache.cocoon.xml.RedundantNamespacesFilter; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; /** * * @version $Id$ */ public class BrowserUpdateTransformer extends AbstractTransformer { public static final String AJAXMODE_PARAM = "cocoon-ajax"; public static final String BU_NSURI = "http://apache.org/cocoon/browser-update/1.0"; private boolean ajaxRequest = false; private int replaceDepth = 0; private boolean inUpdateTag = false; private String updateTagId = null; Locator locator; public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) throws ProcessingException, SAXException, IOException { Request request = ObjectModelHelper.getRequest(objectModel); this.ajaxRequest = request.getParameter(AJAXMODE_PARAM) != null; this.replaceDepth = 0; this.inUpdateTag = false; } public void setDocumentLocator(Locator locator) { super.setDocumentLocator(locator); this.locator = locator; } public void startDocument() throws SAXException { if (ajaxRequest) { // Add the namespace filter to our own output. // This is needed as flattening bu:* elements can lead to some weird reordering of // namespace declarations... RedundantNamespacesFilter nsPipe = new RedundantNamespacesFilter(); if (this.xmlConsumer != null) { nsPipe.setConsumer(this.xmlConsumer); } else { nsPipe.setContentHandler(this.contentHandler); } setConsumer(nsPipe); } super.startDocument(); if (ajaxRequest) { // Add a root element. The original one is very likely to be stripped super.startPrefixMapping("bu", BU_NSURI); super.startElement(BU_NSURI, "document", "bu:document", new AttributesImpl()); } } public void startPrefixMapping(String prefix, String uri) throws SAXException { // Passthrough if not in ajax mode or if in a bu:replace if (!this.ajaxRequest || this.replaceDepth > 0) { super.startPrefixMapping(prefix, uri); } } public void startElement(String uri, String loc, String raw, Attributes attrs) throws SAXException { if (BU_NSURI.equals(uri) && "replace".equals(loc)) { // Keep the id attribute. It may be null, in which case the one of the // child element will be used. this.updateTagId = attrs.getValue("id"); this.inUpdateTag = true; if (this.ajaxRequest && this.replaceDepth == 0) { // Pass this element through super.startElement(uri, loc, raw, attrs); } replaceDepth++; } else { // Passthrough if not in ajax mode or if in a bu:replace // Is the enclosing element a bu:replace? if (this.inUpdateTag) { this.inUpdateTag = false; // Is there already an id? String localId = attrs.getValue("id"); if (localId != null) { // Yes: check it's the same if (this.updateTagId != null && !localId.equals(this.updateTagId)) { throw new SAXParseException("Id on bu:replace (" + this.updateTagId + ") and " + raw + " (" + localId + ") don't match.", this.locator); } } else { // No: add it if (this.updateTagId == null) { throw new SAXParseException("Neither bu:replace nor " + raw + " have an id attribute.", this.locator); } AttributesImpl newAttrs = new AttributesImpl(attrs); newAttrs.addCDATAAttribute("id", this.updateTagId); attrs = newAttrs; } this.updateTagId = null; } if (!this.ajaxRequest || this.replaceDepth > 0) { super.startElement(uri, loc, raw, attrs); } } } public void characters(char[] buffer, int offset, int len) throws SAXException { if (this.inUpdateTag) { // Check that it's only spaces for (int i = offset; i < len; i++) { if (!Character.isWhitespace(buffer[i])) { throw new SAXParseException("bu:replace must include a single child element and no text.", this.locator); } } } if (!this.ajaxRequest || this.replaceDepth > 0) { super.characters(buffer, offset, len); } } public void endElement(String uri, String loc, String raw) throws SAXException { if (BU_NSURI.equals(uri) && "replace".equals(loc)) { replaceDepth--; if (this.ajaxRequest && this.replaceDepth == 0) { // Pass this element through super.endElement(uri, loc, raw); } } else { // Passthrough if not in ajax mode or if in a bu:replace if (!this.ajaxRequest || this.replaceDepth > 0) { super.endElement(uri, loc, raw); } } } public void endPrefixMapping(String prefix) throws SAXException { // Passthrough if not in ajax mode or if in a bu:replace if (!this.ajaxRequest || this.replaceDepth > 0) { super.endPrefixMapping(prefix); } } public void endDocument() throws SAXException { if (ajaxRequest) { super.endElement(BU_NSURI, "document", "bu:document"); super.endPrefixMapping("bu"); } super.endDocument(); } public void recycle() { this.locator = null; this.updateTagId = null; super.recycle(); } }