blob: 5c5b057e40164112bb0de2ce496f8d180b3cb330 [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.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();
}
}