/**
* Copyright (C) 2010 Orbeon, Inc.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version
* 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
*/
package org.orbeon.oxf.xforms.processor.handlers;
import org.orbeon.oxf.xml.XMLReceiver;
import org.orbeon.oxf.xml.*;
import org.xml.sax.*;
import org.xml.sax.helpers.AttributesImpl;
/**
* Intercept SAX output and annotate resulting elements and/or text with classes and spans.
*/
public class OutputInterceptor extends ForwardingXMLReceiver {
private final String spanQName;
private final Listener beginDelimiterListener;
private final boolean isAroundTableOrListElement;
private boolean gotElements = false;
private String delimiterNamespaceURI;
private String delimiterPrefix;
private String delimiterLocalName;
private String addedClasses;
private boolean mustGenerateFirstDelimiters = true;
private int level = 0;
private StringBuilder currentCharacters = new StringBuilder();
protected AttributesImpl reusableAttributes = new AttributesImpl();
public OutputInterceptor(XMLReceiver output, String spanQName, Listener beginDelimiterListener, boolean isAroundTableOrListElement) {
super(output);
this.spanQName = spanQName;
this.beginDelimiterListener = beginDelimiterListener;
this.isAroundTableOrListElement = isAroundTableOrListElement;
// Default to <xhtml:span>
delimiterNamespaceURI = XMLConstants.XHTML_NAMESPACE_URI;
delimiterPrefix = XMLUtils.prefixFromQName(spanQName);
delimiterLocalName = XMLUtils.localNameFromQName(spanQName);
}
public void startElement(String uri, String localname, String qName, Attributes attributes) throws SAXException {
level++;
final boolean topLevelElement = level == 1;
if (! gotElements) {
// Override default as we just go an element
assert topLevelElement;
delimiterNamespaceURI = uri;
delimiterPrefix = XMLUtils.prefixFromQName(qName);
delimiterLocalName = XMLUtils.localNameFromQName(qName);
gotElements = true;
}
flushCharacters(false, topLevelElement);
generateFirstDelimitersIfNeeded();
// Add or update classes on element if needed
super.startElement(uri, localname, qName, topLevelElement ? getAttributesWithClass(attributes) : attributes);
}
public void endElement(String uri, String localname, String qName) throws SAXException {
flushCharacters(false, false);
super.endElement(uri, localname, qName);
level--;
}
public void characters(char[] chars, int start, int length) {
currentCharacters.append(chars, start, length);
}
public void flushCharacters(boolean finalFlush, boolean topLevelCharacters) throws SAXException {
final String currentString = currentCharacters.toString();
if (topLevelCharacters && ! isAroundTableOrListElement) {
// We handle top-level characters specially and wrap them in a span so we can hide them
generateTopLevelSpanWithCharacters(currentCharacters.toString());
} else {
// Just output characters as is in deeper levels, or when around at table or list element
final char[] chars = currentString.toCharArray();
super.characters(chars, 0, chars.length);
}
currentCharacters.setLength(0);
if (finalFlush)
generateFirstDelimitersIfNeeded();
}
private void generateTopLevelSpanWithCharacters(String characters) throws SAXException {
// The first element received determines the type of separator
generateFirstDelimitersIfNeeded();
// Wrap any other text within an xhtml:span
super.startElement(XMLConstants.XHTML_NAMESPACE_URI, "span", spanQName, getAttributesWithClass(SAXUtils.EMPTY_ATTRIBUTES));
final char[] chars = characters.toCharArray();
super.characters(chars, 0, chars.length);
super.endElement(XMLConstants.XHTML_NAMESPACE_URI, "span", spanQName);
}
public void generateFirstDelimitersIfNeeded() throws SAXException {
if (mustGenerateFirstDelimiters) {
// Generate first delimiter
beginDelimiterListener.generateFirstDelimiter(this);
mustGenerateFirstDelimiters = false;
}
}
private Attributes getAttributesWithClass(Attributes originalAttributes) {
String newClassAttribute = originalAttributes.getValue("class");
if (addedClasses != null && addedClasses.length() > 0) {
if (newClassAttribute == null || newClassAttribute.length() == 0) {
newClassAttribute = addedClasses;
} else {
newClassAttribute += " " + addedClasses;
}
}
if (newClassAttribute != null)
return SAXUtils.addOrReplaceAttribute(originalAttributes, "", "", "class", newClassAttribute);
else
return originalAttributes;
}
public boolean isMustGenerateFirstDelimiters() {
return mustGenerateFirstDelimiters;
}
public void setAddedClasses(String addedClasses) {
this.addedClasses = addedClasses;
}
public void outputDelimiter(ContentHandler contentHandler, String classes, String id) throws SAXException {
reusableAttributes.clear();
if (id != null)
reusableAttributes.addAttribute("", "id", "id", XMLReceiverHelper.CDATA, id);
if (classes != null)
reusableAttributes.addAttribute("", "class", "class", XMLReceiverHelper.CDATA, classes);
final String delimiterQName = XMLUtils.buildQName(delimiterPrefix, delimiterLocalName);
contentHandler.startElement(delimiterNamespaceURI, delimiterLocalName, delimiterQName, reusableAttributes);
contentHandler.endElement(delimiterNamespaceURI, delimiterLocalName, delimiterQName);
}
public interface Listener {
public void generateFirstDelimiter(OutputInterceptor outputInterceptor) throws SAXException;
}
}