Java tutorial
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.