client.net.sf.saxon.ce.Xslt20ProcessorImpl.java Source code

Java tutorial

Introduction

Here is the source code for client.net.sf.saxon.ce.Xslt20ProcessorImpl.java

Source

package client.net.sf.saxon.ce;

import client.net.sf.saxon.ce.Controller.APIcommand;
import client.net.sf.saxon.ce.LicenseException;
import client.net.sf.saxon.ce.client.HTTPHandler;
import client.net.sf.saxon.ce.client.HTTPHandler.State;
import client.net.sf.saxon.ce.dom.HTMLDocumentWrapper;
import client.net.sf.saxon.ce.dom.HTMLNodeWrapper;
import client.net.sf.saxon.ce.dom.XMLDOM;
import client.net.sf.saxon.ce.dom.HTMLDocumentWrapper.DocType;
import client.net.sf.saxon.ce.expr.Expression;
import client.net.sf.saxon.ce.expr.XPathContext;
import client.net.sf.saxon.ce.expr.XPathContextMajor;
import client.net.sf.saxon.ce.expr.instruct.GlobalVariable;
import client.net.sf.saxon.ce.js.IXSLFunction;
import client.net.sf.saxon.ce.lib.GenericLogHandler;
import client.net.sf.saxon.ce.lib.JavaScriptAPIException;
import client.net.sf.saxon.ce.lib.NamespaceConstant;
import client.net.sf.saxon.ce.lib.StandardErrorListener;
import client.net.sf.saxon.ce.lib.TraceListener;
import client.net.sf.saxon.ce.om.Axis;
import client.net.sf.saxon.ce.om.DocumentInfo;
import client.net.sf.saxon.ce.om.Item;
import client.net.sf.saxon.ce.om.NamePool;
import client.net.sf.saxon.ce.om.NodeInfo;
import client.net.sf.saxon.ce.om.SequenceIterator;
import client.net.sf.saxon.ce.om.StructuredQName;
import client.net.sf.saxon.ce.om.ValueRepresentation;
import client.net.sf.saxon.ce.pattern.JSObjectPattern;
import client.net.sf.saxon.ce.pattern.NodeKindTest;
import client.net.sf.saxon.ce.pattern.NodeSetPattern;
import client.net.sf.saxon.ce.trace.XSLTTraceListener;
import client.net.sf.saxon.ce.trans.CompilerInfo;
import client.net.sf.saxon.ce.trans.Err;
import client.net.sf.saxon.ce.trans.Mode;
import client.net.sf.saxon.ce.trans.Rule;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.tree.util.URI;
import client.net.sf.saxon.ce.type.ItemType;
import client.net.sf.saxon.ce.value.SequenceType;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.http.client.Request;
import com.google.gwt.http.client.RequestBuilder;
import com.google.gwt.http.client.RequestCallback;
import com.google.gwt.http.client.RequestTimeoutException;
import com.google.gwt.http.client.Response;
import com.google.gwt.logging.client.LogConfiguration;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.Window;
//import com.google.gwt.xml.client.XMLParser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;

import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.timepedia.exporter.client.ExporterUtil;

/**
 * This class represents the XSLT 2.0 processor, which is the top-level object implemented by Saxon-CE
 * Logging is implemented using GWT Logging - documentation at: 
 * http://code.google.com/webtoolkit/doc/latest/DevGuideLogging.html
 */
public class Xslt20ProcessorImpl implements EntryPoint {

    final Configuration config = new Configuration();
    private boolean registeredForEvents = false;
    private boolean principleEventListener = false;
    PreparedStylesheet stylesheet = null;
    private JavaScriptObject successCallback = null;
    Controller localController = new Controller(config, true);
    private static Logger logger = Logger.getLogger("XSLT20Processor");

    public void onModuleLoad() {
        if (LogConfiguration.loggingIsEnabled()) {
            SaxonceApi.setAnyExternalErrorHandler();
            LogController.initLogger();
            LogController.addJavaScriptLogHandler();
        }

        logger.log(Level.FINE, "GWT Module Load initated by page: " + Document.get().getTitle());
        if (LogConfiguration.loggingIsEnabled()) {
            String href = Window.Location.getHref();
            if (href != null && href.startsWith("file:")) {
                logger.warning(
                        "The file:// protocol in use may cause 'permission denied' errors in Saxon-CE - unless the browser's 'strict-origin-policy' has been relaxed.");
            }
        }
        //register();
        ExporterUtil.exportAll();
        SaxonceApi.register();

        // License code commented out - change 1.1 from decision: saxon-ce opensource     
        /*        try {
                   Verifier.loadLicense();
                   if (LogConfiguration.loggingIsEnabled()){
          Verifier.displayLicenseMessage();
                   }
              
                } catch (LicenseException le) {
                   handleException(le, "onModuleLoad");
                   return;
                } */

        // perform JavaScript callback or use <script> element to initate transform
        JavaScriptObject saxonceLoadCallback = getCallback();
        if (saxonceLoadCallback != null) {
            logger.log(Level.FINE, "Executing 'onSaxonceLoad' callback...");
            try {
                executeCallback(saxonceLoadCallback);
            } catch (JavaScriptException jse) {
                handleException(jse, "onModuleLoad");
            } catch (Exception je) {
                handleException(je, "onModuleLoad");
            }
        }
        // do transform based on <script> element - this might be needed as
        // as a callback - for test-harness for example
        doTransformation();
    }

    private native JavaScriptObject getCallback() /*-{
                                                  if ($wnd.onSaxonLoad && typeof $wnd.onSaxonLoad == 'function') {
                                                  return $wnd.onSaxonLoad;
                                                  } else {
                                                  return null;
                                                  }
                                                  }-*/;

    // sets the window object as the owner object
    private native void executeCallback(JavaScriptObject callback) /*-{
                                                                   callback.apply($wnd);
                                                                   }-*/;

    // script nodes found when module loaded
    private static NodeList<Element> scriptsOnLoad;

    /**
     * Gets script element list the first time this is called
     * Used by Verifier and Xslt20Processor.doTransformation()
     */
    public static NodeList<Element> getScriptsOnLoad() {
        if (scriptsOnLoad == null) {
            scriptsOnLoad = Document.get().getElementsByTagName("s" + "cript");
        }
        return scriptsOnLoad;
    }

    public JavaScriptObject getSuccess() {
        return successCallback;
    }

    public XSLT20Processor successOwner = null;

    public void setSuccess(JavaScriptObject sFunction, XSLT20Processor sOwner) {
        successCallback = sFunction;
        successOwner = sOwner;
    }

    /**
     * The entry point for transforms initiate on module load,
     * fetches settings from the <code>application/xslt+xml style</code> element
     */
    public void doTransformation() {
        Logger logger = Logger.getLogger("Xstl20Processor");
        try {
            NodeList<Element> scripts = getScriptsOnLoad();
            String sourceURI = null;
            String styleURI = null;
            String initialMode = null;
            String initialTemplate = null;
            boolean styleElementExists = false;
            for (int i = 0; i < scripts.getLength(); i++) {
                String type = scripts.getItem(i).getAttribute("type");
                if (type.equals("application/xslt+xml")) {
                    styleElementExists = true;
                    styleURI = scripts.getItem(i).getAttribute("src");
                    sourceURI = scripts.getItem(i).getAttribute("data-source");
                    initialMode = scripts.getItem(i).getAttribute("data-initial-mode");
                    initialTemplate = scripts.getItem(i).getAttribute("data-initial-template");
                    break;
                }
            }

            if (!styleElementExists) {
                logger.info("Saxon-CE API initialised");
                return;
            } else if (styleURI == null) {
                throw new XPathException("No XSLT stylesheet reference found");
            }

            JavaScriptObject sourceDoc = null;
            String absSourceURI = null;
            if (sourceURI != null && sourceURI.length() != 0) {
                String pageHref = Window.Location.getHref();
                absSourceURI = (new URI(pageHref).resolve(sourceURI)).toString();
                if (pageHref.equals(absSourceURI)) {
                    throw new XPathException("Cannot load XML with same URI as the host page");
                }

                sourceDoc = SaxonceApi.createAsyncDoc(absSourceURI); // config.buildDocument(absSourceURI);

            } else if (initialTemplate == null) {
                throw new XPathException(
                        "No data-source attribute or data-initial-template value found - one is required");
            }

            String absStyleURI = (new URI(Window.Location.getHref()).resolve(styleURI)).toString();
            DocumentInfo styleDoc;
            try {
                styleDoc = config.buildDocument(absStyleURI);
            } catch (XPathException e) {
                String reportURI = (absSourceURI != null) ? absSourceURI : styleURI;
                throw new XPathException("Failed to load XSLT stylesheet " + reportURI + ": " + e.getMessage());
            }
            config.getDocumentPool().add(styleDoc, absStyleURI); // where document('') can find it
            Element body = getBodyElement();

            localController.setInitialMode(initialMode);
            localController.setInitialTemplate(initialTemplate);
            localController.setApiCommand(APIcommand.UPDATE_HTML);
            localController.setTargetNode(Document.get()); // target node is the document node

            renderXML(sourceDoc, styleDoc, body); // principle output is to the body element

        } catch (Exception err) {
            logger.log(Level.SEVERE, err.getMessage());
        }
    }

    public static Element getBodyElement() {
        Element body = Document.get().getElementsByTagName("BODY").getItem(0);
        if (body == null) {
            body = Document.get().getElementsByTagName("body").getItem(0);
        }
        return body;
    }

    public void continueWithSourceDocument(final DocumentInfo sourceDoc, final String styleURI,
            final String initialMode, final String initialTemplate) {
        //        RequestBuilder request = new RequestBuilder(RequestBuilder.GET, styleURI);
        //        request.setHeader("Cache-Control", "no-cache");
        //        request.setCallback(new RequestCallback() {
        //            public void onResponseReceived(Request request, Response response) {
        //                com.google.gwt.xml.client.Document styleXML = XMLParser.parse(response.getText());
        //                DocumentInfo styleDoc = new XMLDocumentWrapper(styleXML, styleURI, config);
        //                renderXML(sourceDoc, styleDoc, initialMode, initialTemplate, Document.get().getElementById("target"));
        //            }
        //            public void onError(Request request, Throwable exception) {
        //                Window.alert(exception.getMessage());
        //            }
        //        });
    }

    /**
     * Implementation of XSLT20Processor API - only handles xml dom - must
     * use transformToFragment or updateHTMLDocument for html dom
     * @param sourceDoc
     */
    public void updateHTMLDocument(JavaScriptObject sourceDoc, Document targetDoc, APIcommand cmd) {
        if (targetDoc == null) {
            targetDoc = Document.get();
        }
        localController.setApiCommand(cmd);
        localController.setTargetNode(targetDoc);
        renderXML(sourceDoc, importedStylesheet, getBodyElement());
    }

    public Node transformToDocument(JavaScriptObject sourceDoc) {
        localController.setTargetNode(XMLDOM.createDocument(localController.getBaseOutputURI()));
        localController.setApiCommand(APIcommand.TRANSFORM_TO_DOCUMENT);
        Document targetDoc = XMLDOM.createDocument(localController.getBaseOutputURI());
        return renderXML(sourceDoc, importedStylesheet, targetDoc);
    }

    public Node transformToFragment(JavaScriptObject sourceDoc, Document ownerDocument) {
        Document owner = (ownerDocument == null) ? XMLDOM.createDocument(localController.getBaseOutputURI())
                : ownerDocument;
        Node targetDocumentFragment = HTMLDocumentWrapper.createDocumentFragment(owner);
        // Set the owner document for result document output fragments:
        localController.setTargetNode(owner);
        localController.setApiCommand(APIcommand.TRANSFORM_TO_FRAGMENT);
        return renderXML(sourceDoc, importedStylesheet, targetDocumentFragment);
    }

    private DocumentInfo importedStylesheet;

    /**
     * Imports new stylesheet and de-registers sinks for specific events from
     * any previous stylesheet for this processor
     * @param doc
     */
    public void importStylesheet(JavaScriptObject doc) {
        deregisterEventHandlers();
        try {
            importedStylesheet = SaxonceApi.getDocSynchronously(doc, config);
        } catch (XPathException e) {
            handleException(e, "importStylesheet");
        }
    }

    // fetched either synchronously or asynchronously
    NodeInfo fetchedSourceDoc;
    boolean transformInvoked;
    boolean docFetchRequired;

    public Node renderXML(JavaScriptObject inSourceDoc, DocumentInfo styleDoc,
            com.google.gwt.dom.client.Node target) {
        try {
            if (styleDoc == null) {
                throw new Exception("Stylesheet for transform is null");
            }
            docFetchRequired = inSourceDoc != null;
            CompilerInfo info = config.getDefaultXsltCompilerInfo();
            info.setErrorListener(new StandardErrorListener());

            String asyncSourceURI = null;

            // for now - don't use aync when using the JavaScript API calls that return a result
            if (docFetchRequired
                    && (localController.getApiCommand() == APIcommand.UPDATE_HTML || (successCallback != null))) {
                asyncSourceURI = SaxonceApi.getAsyncUri(inSourceDoc);
                if (asyncSourceURI != null && asyncSourceURI.toLowerCase().startsWith("file:")) {
                    asyncSourceURI = null; // force synchronous fetch if using file-system protocol
                }
            }

            // ----------- Start async code -------------
            fetchedSourceDoc = null;
            transformInvoked = false;

            if (asyncSourceURI != null) {
                final String URI = asyncSourceURI;
                final Node transformTarget = target;

                logger.log(Level.FINE, "Aynchronous GET for: " + asyncSourceURI);
                final HTTPHandler hr = new HTTPHandler();

                hr.doGet(asyncSourceURI, new RequestCallback() {

                    public void onError(Request request, Throwable exception) {
                        //hr.setErrorMessage(exception.getMessage());
                        String msg = "HTTP Error " + exception.getMessage() + " for URI " + URI;
                        handleException(new RuntimeException(msg), "onError");
                    }

                    public void onResponseReceived(Request request, Response response) {
                        int statusCode = response.getStatusCode();
                        if (statusCode == 200) {
                            Logger.getLogger("ResponseReceived").fine("GET Ok for: " + URI);
                            Node responseNode;
                            try {
                                responseNode = (Node) XMLDOM.parseXML(response.getText());
                            } catch (Exception e) {
                                handleException(new RuntimeException(e.getMessage()), "onResponseReceived");
                                return;
                            }
                            DocumentInfo responseDoc = config.wrapXMLDocument(responseNode, URI);
                            // now document is here, we can transform it
                            Node result = invokeTransform(responseDoc, transformTarget);
                            hr.setResultNode(result); // TODO: This isn't used yet
                            // handle OK response from the server 
                        } else if (statusCode < 400) {
                            // transient
                        } else {
                            String msg = "HTTP Error " + statusCode + " " + response.getStatusText() + " for URI "
                                    + URI;
                            handleException(new RuntimeException(msg), "onResponseReceived");
                            //hr.setErrorMessage(statusCode + " " + response.getStatusText());
                        }
                    } // ends inner method
                }// ends inner class
                ); // ends doGet method call
            }
            // -------------- End async code

            /// we can compile - even while sourcedoc is being fetched asynchronously

            if (stylesheet == null) {
                if (LogConfiguration.loggingIsEnabled()) {
                    LogController.InitializeTraceListener();
                }
                logger.log(Level.FINE, "Compiling Stylesheet...");
                PreparedStylesheet sheet = new PreparedStylesheet(config, info);
                sheet.prepare(styleDoc);
                stylesheet = sheet;
                logger.log(Level.FINE, "Stylesheet compiled OK");
            }

            // for async operation - this is called within the callback - so don't call here            
            if (asyncSourceURI == null && inSourceDoc != null) {
                int nodeType = (Node.is(inSourceDoc)) ? ((Node) inSourceDoc).getNodeType() : 0;

                if (nodeType > 0 && nodeType != Node.DOCUMENT_NODE) {
                    // add a document node wrapper
                    Node sourceNode = (Node) inSourceDoc;
                    Document sourceDoc = sourceNode.getOwnerDocument();
                    HTMLDocumentWrapper htmlDoc = new HTMLDocumentWrapper(sourceDoc, sourceDoc.getURL(), config,
                            DocType.UNKNOWN);
                    fetchedSourceDoc = htmlDoc.wrap(sourceNode);
                } else {
                    fetchedSourceDoc = SaxonceApi.getDocSynchronously(inSourceDoc, config);
                }
            }
            // this method only runs if transformInvoked == false - need to get sourceDoc reference if not invoked

            return invokeTransform(fetchedSourceDoc, target);

            //method ends - allowing onResponceReceived handler to call invokeTransform for async operation
        } catch (Exception e) {
            handleException(e, "renderXML");
            return null;
        }
    }

    public static native boolean isNonDocNode(JavaScriptObject obj) /*-{
                                                                    return (typeof obj.getNodeType == "function" && obj.getNodeType() != 9);
                                                                    }-*/;

    private List<Mode> registeredEventModes = null;
    private boolean registeredProcessorForNonDomEvents = false;

    private void registerNonDOMevents(Controller controller) throws XPathException {
        for (Mode eventMode : registeredEventModes) {
            ArrayList<Rule> nonDomRules = eventMode.getVirtualRuleSet();
            if (nonDomRules != null) {
                if (!registeredProcessorForNonDomEvents) {
                    registeredProcessorForNonDomEvents = true;
                    Controller.addNonDomEventProcessor(this);
                }
                String eventName = eventMode.getModeName().getLocalName();
                for (Rule r : nonDomRules) {
                    JavaScriptObject eventTarget = null;
                    eventTarget = ((JSObjectPattern) r.getPattern()).evaluate(controller.newXPathContext());
                    bindTemplateToWindowEvent(eventName, eventTarget);
                }
            }

        }

    }

    public static native String bindTemplateToWindowEvent(String eventName, JavaScriptObject target) /*-{
                                                                                                     target[eventName] = $entry(function(eventArg) {@client.net.sf.saxon.ce.Xslt20ProcessorImpl::relayNonDomEvent(Ljava/lang/String;Lcom/google/gwt/core/client/JavaScriptObject;Lcom/google/gwt/core/client/JavaScriptObject;)(eventName, target, eventArg)});
                                                                                                     }-*/;

    public static void relayNonDomEvent(String name, JavaScriptObject obj, JavaScriptObject eventArg) {
        JavaScriptObject event = (eventArg == null) ? getWindowEvent() : eventArg;
        Controller.relayNonDomEvent(name, obj, event);
    }

    public static native JavaScriptObject getWindowEvent() /*-{
                                                           return $wnd.event;
                                                           }-*/;

    private void registerEventHandlers(Controller controller) throws XPathException {
        // add an event listener to capture registered event modes
        if (registeredEventModes != null) {
            return;
        }
        Element docElement = (com.google.gwt.user.client.Element) (Object) Document.get();
        registeredEventModes = controller.getRuleManager().getModesInNamespace(NamespaceConstant.IXSL);
        // Restriction: only one event listener per element
        if (registeredEventModes.size() > 0 && !registeredForEvents) {
            registeredForEvents = true;
            registerNonDOMevents(controller);
            if (DOM.getEventListener((com.google.gwt.user.client.Element) docElement) == null) {
                principleEventListener = true;
                DOM.setEventListener((com.google.gwt.user.client.Element) docElement, new EventListener() {
                    public void onBrowserEvent(Event event) {

                        EventTarget eTarget = event.getEventTarget();
                        Node eventNode;
                        if (Node.is(eTarget)) {
                            eventNode = Node.as(eTarget);
                        } else {
                            eventNode = Node.as(getCorrespondingSVGElement(eTarget));
                            if (eventNode == null) {
                                return;
                            }
                        }
                        bubbleApplyTemplates(eventNode, event);
                    }
                });
            } else {
                // can't register for event so register for relayEvent
                Controller.addEventProcessor(this);
            }
        }
        // Events for all processor instances may register 1 or more event types
        for (Mode eventMode : registeredEventModes) {
            String eventName = eventMode.getModeName().getLocalName();
            if (!eventName.startsWith("on")) {
                logger.warning("Event name: '" + eventName + "' is invalid - names should begin with 'on'");
            } else {
                eventName = eventName.substring(2);
            }
            int eventNo = Event.getTypeInt(eventName);
            DOM.sinkEvents((com.google.gwt.user.client.Element) docElement,
                    eventNo | DOM.getEventsSunk((com.google.gwt.user.client.Element) docElement));

        }

    }

    public void deregisterEventHandlers() {
        // Don't deregister because this will affect any other instances of the processor
        //       Element docElement = (com.google.gwt.user.client.Element)(Object)Document.get();       
        //       DOM.sinkEvents((com.google.gwt.user.client.Element)docElement, 0);
        //       registeredEventModes = null;
    }

    /**
     * Returns the SVG DOM element associated with an external element that's an SVGElementInstance object
     * E.g. If a <code>use</code> element references a <code>rect</code> element - using xlink:href - then,
     * if the <code>rect</code> object is passed as a parameter, the <code>use</code> element is returned.
     */
    public native JavaScriptObject getCorrespondingSVGElement(JavaScriptObject obj) /*-{
                                                                                    if (obj.correspondingElement) {
                                                                                    return obj.correspondingElement;
                                                                                    }
                                                                                    return null;
                                                                                    }-*/;

    /**
     * This invokes a transform, but it may be called either on an async callback or directly
     * We need to ensure this method runs once and only once for a single transform request -
     * It's possible for either the main code branch which performs the compile or the async callback
     * which depends on the compile to makes the call first.
     */
    private Node invokeTransform(NodeInfo inDoc, com.google.gwt.dom.client.Node target) {
        // in case stylesheet not ready but doc has been fetched:
        if (fetchedSourceDoc == null) {
            fetchedSourceDoc = inDoc;
        }
        // check to ensure conditions required for a transform have been met
        if (transformInvoked || stylesheet == null || (docFetchRequired && fetchedSourceDoc == null)) {
            return null;
        }
        transformInvoked = true;

        try {
            final Controller controller = stylesheet.newTransformer();
            localController.setSourceNode(fetchedSourceDoc);
            controller.importControllerSettings(localController);
            logger.log(Level.FINE, "Commencing transform type:" + controller.getApiCommand().toString());
            Node outResult = controller.transform(fetchedSourceDoc, target);
            logger.log(Level.FINE, "Transform complete");
            localController.importResults(controller);
            registerEventHandlers(controller);
            if (successCallback != null) {
                successOwner.invokeSuccess(successCallback);
            }
            return outResult;
        } catch (Exception e) {
            handleException(e, "invokeTransform");
            return null;
        }
    }

    public static void handleException(Exception err, String prefix) {
        if (err instanceof JavaScriptAPIException) {
            // don't log and re-throw exceptions thrown only for the benefit of the API
            return;
        }
        String excName;
        prefix = (prefix == null || prefix.length() == 0) ? "" : " in " + prefix + ":";
        boolean logException = true;

        if (err instanceof XPathException) {
            excName = "XPathException";
            XPathException xe = (XPathException) err;
            logException = !xe.hasBeenReported();
        } else if (err instanceof LicenseException) {
            excName = "LicenseException";
        } else {
            excName = "Exception " + err.getClass().getName();
        }
        String message = excName + prefix + " " + err.getMessage();
        if (logException) {
            logger.log(Level.SEVERE, message);
        }
        err.printStackTrace();
        if (SaxonceApi.doThrowJsExceptions()) {
            throw new JavaScriptAPIException("[js] " + message);
        }
    }

    // emulate bubbling up events by iterating through ancestors that have matching rule, starting with the
    // targetNode

    private Mode getModeFromEvent(Event event) {
        Mode result = null;
        String mode = "on" + event.getType(); // eg. onclick
        for (Mode m : registeredEventModes) {
            if (m.getModeName().getLocalName().equals(mode)) {
                result = m;
                break;
            }
        }
        return result;
    }

    public void bubbleApplyTemplates(Node node, Event event) {
        if (principleEventListener) {
            Controller.relayEvent(node, event); // make a call to this method for other instances
        }
        NodeInfo eventNode = ((HTMLDocumentWrapper) config.getHostPage()).wrap(node);
        SequenceIterator bubbleElements = eventNode.iterateAxis(Axis.ANCESTOR, NodeKindTest.ELEMENT);
        Controller controller = stylesheet.newTransformer();
        try {
            controller.importControllerSettings(localController);
            XPathContext ruleContext = controller.newXPathContext();
            Mode matchedMode = getModeFromEvent(event);

            if (matchedMode == null) {
                return;
            }

            // walk up the tree until we find an element with matching rule for the event mode

            NodeInfo element = eventNode;
            while (element != null) {
                Rule matchedRule = matchedMode.getRule(element, ruleContext);
                if (matchedRule != null && eventPropertyMatch(event, matchedRule)) {
                    logger.log(Level.FINER,
                            "Bubble Apply-Templates - Mode: " + matchedMode.getModeName().getLocalName()
                                    + " Element: " + controller.getNamePool().getLocalName(element.getNameCode()));
                    applyEventTemplates(matchedMode.getModeName().getClarkName(), element, event, null);
                    if (matchedRule.getIxslPreventDefault()) {
                        event.preventDefault();
                    }
                    break;
                }
                element = (NodeInfo) bubbleElements.next();
            }
        } catch (Exception e) {
            handleException(e, "bubbleApplyTemplates");
        }
    }

    private static boolean eventPropertyMatch(Event event, Rule matchedRule) {
        String eventProperty = matchedRule.getEventProperty();
        if (eventProperty == null) {
            return true;
        }
        String[] all = eventProperty.split("\\s");
        String eventPropertyValue = getEventProperty(event, all[0]);
        if (all.length < 2 || eventPropertyValue == null) {
            return true;
        }
        boolean matches = false;

        for (int i = 1; i < all.length; i++) {
            if (eventPropertyValue.equals(all[i])) {
                matches = true;
                break;
            }
        }
        return matches;
    }

    private static native String getEventProperty(Event evt, String propName) /*-{
                                                                              var prop = evt[propName];
                                                                              if (prop != null) {
                                                                              return String(prop);
                                                                              }
                                                                              return null;
                                                                              }-*/;

    // called by bubbleApplyTemplates which is the registered event handler
    // at the document node level for all specified ixsl modes      
    public void applyEventTemplates(String mode, NodeInfo start, JavaScriptObject event, JavaScriptObject object) {
        try {
            // for case where this is a non-user event - to prevent error
            if (start == null) {
                start = config.getHostPage();
            }
            logger.log(Level.FINER, "OnEvent Apply-Templates - Mode: " + mode + " Event: " + event.toString());
            Controller controller = stylesheet.newTransformer();

            controller.importControllerSettings(localController);
            // override any imported initial mode with that for the event
            controller.setInitialTemplate(null);
            controller.setInitialMode(mode);
            controller.setUserData("Saxon-CE", "current-event", event);
            controller.setUserData("Saxon-CE", "current-object", object);

            controller.transform(start, controller.getTargetNode());
        } catch (Exception err) {
            handleException(err, "mode: '" + mode + "' event: '" + event.toString());
        }
    }

    public Controller getController() {
        return localController;
    }
}

// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. 
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is Incompatible With Secondary Licenses?, as defined by the Mozilla Public License, v. 2.0.