package org.tigris.juxy.builder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.tigris.juxy.XSLTKeys;
import org.tigris.juxy.Tracer;
import org.tigris.juxy.util.StringUtil;
import org.tigris.juxy.util.XSLTEngineSupport;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.XMLFilterImpl;
import java.util.*;
/**
* @author Pavel Sher
*/
public class TracingFilter extends XMLFilterImpl {
private final static Log logger = LogFactory.getLog(TracingFilter.class);
private Locator locator;
private List saxEvents = new ArrayList(20);
private Map namespaces = new HashMap();
private boolean withinTemplateElement = false;
private static int MAX_TEXT_LEN = 51;
private XSLTEngineSupport engineSupport;
public TracingFilter(final XSLTEngineSupport engineSupport) {
this.engineSupport = engineSupport;
}
public void startDocument() throws SAXException {
logger.debug("Start augmenting stylesheet with tracing code: " + locator.getSystemId() + " ...");
super.startDocument();
}
public void endDocument() throws SAXException {
logger.debug("End augmenting stylesheet with tracing code");
super.endDocument();
}
public void setDocumentLocator(Locator locator) {
this.locator = locator;
super.setDocumentLocator(locator);
}
public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
try {
if (!isTemplateElement(uri, localName) && !withinTemplateElement) {
super.startElement(uri, localName, qName, atts);
return;
}
withinTemplateElement = true;
appendSAXEvent(new StartElementEvent(uri, localName, qName, atts));
} finally {
namespaces.clear();
}
}
private void startElement0(String uri, String localName, String qName, Attributes atts) throws SAXException {
super.startElement(uri, localName, qName, atts);
}
public void endElement(String uri, String localName, String qName) throws SAXException {
if (!withinTemplateElement) {
super.endElement(uri, localName, qName);
return;
}
appendSAXEvent(new EndElementEvent(uri, localName, qName));
if (isTemplateElement(uri, localName)) {
withinTemplateElement = false;
flushEvents();
}
}
private void endElement0(String uri, String localName, String qName) throws SAXException {
super.endElement(uri, localName, qName);
}
public void startPrefixMapping(String prefix, String uri) throws SAXException {
namespaces.put(prefix, uri);
super.startPrefixMapping(prefix, uri);
}
public void endPrefixMapping(String prefix) throws SAXException {
namespaces.remove(prefix);
super.endPrefixMapping(prefix);
}
public void startPrefixMapping0(String prefix, String uri) throws SAXException {
super.startPrefixMapping(prefix, uri);
}
public void endPrefixMapping0(String prefix) throws SAXException {
super.endPrefixMapping(prefix);
}
public void characters(char ch[], int start, int length) throws SAXException {
appendSAXEvent(new CharactersEvent(ch, start, length));
}
private void characters0(char ch[], int start, int length) throws SAXException {
super.characters(ch, start, length);
}
public void processingInstruction(String target, String data) throws SAXException {
appendSAXEvent(new PiEvent(target, data));
}
public void processingInstruction0(String target, String data) throws SAXException {
super.processingInstruction(target, data);
}
private void flushEvents() throws SAXException {
if (saxEvents.size() == 0) return;
LinkedList allEvents = new LinkedList();
List postponedEvents = new ArrayList(20);
int i = 0;
int level = 0;
boolean inXslText = false;
while (i < saxEvents.size()) {
SAXEvent event = (SAXEvent) saxEvents.get(i);
if (event instanceof StartElementEvent) {
level++;
StartElementEvent startEvent = (StartElementEvent) event;
inXslText = isXslTextElement(startEvent.uri, startEvent.localName);
if (isTemplateElement(startEvent.uri, startEvent.localName)) {
allEvents.add(startEvent);
postponedEvents.addAll(generateTracingEvents(tagName(startEvent), startEvent, level));
i++;
} else {
if (isParamElement(startEvent.uri, startEvent.localName)) {
allEvents.add(startEvent);
i++;
continue;
} else {
allEvents.addAll(postponedEvents);
postponedEvents.clear();
}
if (isAugmented(startEvent.uri, startEvent.localName) && !isAugmentedAfterStart(startEvent.uri, startEvent.localName))
allEvents.addAll(generateTracingEvents(tagName(startEvent), startEvent, level));
allEvents.add(startEvent);
if (isAugmented(startEvent.uri, startEvent.localName) && isAugmentedAfterStart(startEvent.uri, startEvent.localName))
allEvents.addAll(generateTracingEvents(tagName(startEvent), startEvent, level));
i++;
}
}
if (event instanceof EndElementEvent) {
EndElementEvent endEvent = (EndElementEvent) event;
if (isXslTextElement(endEvent.uri, endEvent.localName)) {
inXslText = false;
allEvents.add(event);
}
if (!isParamElement(endEvent.uri, endEvent.localName) && !inXslText) {
allEvents.addAll(postponedEvents);
postponedEvents.clear();
}
if (!isXslTextElement(endEvent.uri, endEvent.localName))
allEvents.add(event);
level--;
i++;
}
if (event instanceof CharactersEvent) {
List generatedEvents = generateCharactersTracingEvents(i, level);
if (generatedEvents.size() > 0) {
if (inXslText) {
postponedEvents.addAll(generatedEvents);
} else {
allEvents.addAll(postponedEvents);
postponedEvents.clear();
allEvents.addAll(generatedEvents);
}
}
allEvents.add(event);
i++;
for (; i < saxEvents.size(); i++) {
SAXEvent ev = (SAXEvent) saxEvents.get(i);
if (!(ev instanceof CharactersEvent))
break;
allEvents.add(ev);
}
}
if (event instanceof PiEvent) {
postponedEvents.addAll(generateTracingEvents((PiEvent) event, level));
allEvents.add(event);
i++;
}
}
Iterator it = allEvents.iterator();
while (it.hasNext()) {
SAXEvent event = (SAXEvent) it.next();
event.generate();
}
saxEvents.clear();
}
private List generateCharactersTracingEvents(int startIdx, int level) {
List result = new ArrayList(5);
StringBuffer text = new StringBuffer(30);
int line = -1;
for (int i = startIdx; i < saxEvents.size(); i++) {
SAXEvent e = (SAXEvent) saxEvents.get(i);
if (!(e instanceof CharactersEvent))
break;
CharactersEvent ce = (CharactersEvent) e;
if (line == -1)
line = ce.line;
String et = ce.getString();
text.append(StringUtil.collapseSpaces(et, StringUtil.SPACE_AND_CARRIAGE_CHARS));
if (i + 1 >= saxEvents.size() || !(saxEvents.get(i + 1) instanceof CharactersEvent)) {
if (text.length() >= MAX_TEXT_LEN) {
text.delete(MAX_TEXT_LEN - 1, text.length());
text.append(" ...");
}
String trimmedText = text.toString().trim();
if (trimmedText.length() > 0)
result.addAll(generateTracingEvents(trimmedText, line, ce.systemId, level));
}
}
return result;
}
private List generateTracingEvents(PiEvent event, int level) {
return generateTracingEvents("<?" + event.target + (event.data.length() > 0 ? " " + event.data : "") + "?>", event, level);
}
private List generateTracingEvents(String tracingText, SAXEvent event, int level) {
return generateTracingEvents(tracingText, event.line, event.systemId, level);
}
private List generateTracingEvents(String tracingText, int line, String systemId, int level) {
List events = new ArrayList(6);
AttributesImpl a = new AttributesImpl();
a.addAttribute("", "select", "select", "CDATA",
JuxyParams.TRACER_EXTENSION_NAME + ":trace(" +
line + ", " +
level + ", '" +
escapeSingleQuot(systemId) + "', '" +
escapeSingleQuot(tracingText) + "')");
events.add(new StartPrefixMappingEvent(JuxyParams.TRACER_EXTENSION_NAME, engineSupport.getJavaExtensionNamespace(Tracer.class)));
events.add(new StartElementEvent(XSLTKeys.XSLT_NS, "value-of", "xsl:value-of", a));
events.add(new EndElementEvent(XSLTKeys.XSLT_NS, "value-of", "xsl:value-of"));
events.add(new EndPrefixMappingEvent(JuxyParams.TRACER_EXTENSION_NAME));
return events;
}
private String tagName(StartElementEvent startEvent) {
StringBuffer msg = new StringBuffer(10);
msg.append("<").append(startEvent.qName);
Iterator it = startEvent.elemNamespaces.entrySet().iterator();
while (it.hasNext()) {
Map.Entry e = (Map.Entry) it.next();
msg.append(" xmlns:").append(e.getKey()).append("=\"").append(escapeSingleQuot((String) e.getValue())).append("\"");
}
for (int i = 0; i < startEvent.atts.getLength(); i++) {
msg.append(" ").append(startEvent.atts.getQName(i));
msg.append("=\"").append(startEvent.atts.getValue(i)).append("\"");
}
msg.append(">");
return msg.toString();
}
private String escapeSingleQuot(String str) {
return StringUtil.replaceCharByEntityRef(str, '\'');
}
private void appendSAXEvent(SAXEvent event) {
saxEvents.add(event);
}
private boolean isTemplateElement(String uri, String localName) {
return XSLTKeys.XSLT_NS.equals(uri) && localName.equals("template");
}
private boolean isXslTextElement(String uri, String localName) {
return XSLTKeys.XSLT_NS.equals(uri) && localName.equals("text");
}
private boolean isParamElement(String uri, String localName) {
return XSLTKeys.XSLT_NS.equals(uri) && localName.equals("param");
}
private boolean isAugmented(String uri, String localName) {
return !(XSLTKeys.XSLT_NS.equals(uri) && NOT_AUGMENTED_STATEMENTS.contains(localName));
}
private boolean isAugmentedAfterStart(String uri, String localName) {
return XSLTKeys.XSLT_NS.equals(uri) && AUGMENTED_AFTER_START.contains(localName);
}
private static final Set AUGMENTED_AFTER_START = new HashSet();
static {
AUGMENTED_AFTER_START.add("for-each");
AUGMENTED_AFTER_START.add("for-each-group");
AUGMENTED_AFTER_START.add("otherwise");
AUGMENTED_AFTER_START.add("template");
AUGMENTED_AFTER_START.add("when");
AUGMENTED_AFTER_START.add("matching-substring");
AUGMENTED_AFTER_START.add("non-matching-substring");
AUGMENTED_AFTER_START.add("fallback");
AUGMENTED_AFTER_START.add("attribute");
AUGMENTED_AFTER_START.add("namespace");
}
private static final Set NOT_AUGMENTED_STATEMENTS = new HashSet();
static {
NOT_AUGMENTED_STATEMENTS.add("with-param");
NOT_AUGMENTED_STATEMENTS.add("param");
NOT_AUGMENTED_STATEMENTS.add("sort");
}
abstract class SAXEvent {
public int line;
public int column;
public String systemId;
public SAXEvent() {
this.line = locator.getLineNumber();
this.column = locator.getColumnNumber();
this.systemId = locator.getSystemId();
}
public abstract void generate() throws SAXException;
}
abstract class ElementEvent extends SAXEvent {
public String uri;
public String localName;
public String qName;
public ElementEvent(String uri, String localName, String qName) {
this.uri = uri;
this.localName = localName;
this.qName = qName;
}
}
class StartElementEvent extends ElementEvent {
public Attributes atts;
public Map elemNamespaces = new HashMap();
public StartElementEvent(String uri, String localName, String qName, Attributes atts) {
super(uri, localName, qName);
this.atts = new AttributesImpl(atts);
this.elemNamespaces.putAll(namespaces);
}
public void generate() throws SAXException {
startElement0(uri, localName, qName, atts);
}
}
class EndElementEvent extends ElementEvent {
public EndElementEvent(String uri, String localName, String qName) {
super(uri, localName, qName);
}
public void generate() throws SAXException {
endElement0(uri, localName, qName);
}
}
class StartPrefixMappingEvent extends SAXEvent {
public String prefix;
public String uri;
public StartPrefixMappingEvent(String prefix, String uri) {
this.prefix = prefix;
this.uri = uri;
}
public void generate() throws SAXException {
startPrefixMapping0(prefix, uri);
}
}
class EndPrefixMappingEvent extends SAXEvent {
public String prefix;
public EndPrefixMappingEvent(String prefix) {
this.prefix = prefix;
}
public void generate() throws SAXException {
endPrefixMapping0(prefix);
}
}
class CharactersEvent extends SAXEvent {
public char[] chars;
public int start;
public int length;
public CharactersEvent(char ch[], int start, int length) {
this.chars = new char[length];
System.arraycopy(ch, start, chars, 0, length);
this.start = 0;
this.length = length;
}
public String getString() {
StringBuffer buf = new StringBuffer(20);
for (int i = start; i < start + length; i++) {
buf.append(chars[i]);
}
return buf.toString();
}
public void generate() throws SAXException {
characters0(chars, start, length);
}
}
class PiEvent extends SAXEvent {
public String target;
public String data;
public PiEvent(String target, String data) {
this.target = target;
this.data = data;
}
public void generate() throws SAXException {
processingInstruction0(target, data);
}
}
}