package client.net.sf.saxon.ce;
import client.net.sf.saxon.ce.dom.HTMLDocumentWrapper.DocType;
import client.net.sf.saxon.ce.dom.HTMLWriter;
import client.net.sf.saxon.ce.event.*;
import client.net.sf.saxon.ce.expr.XPathContext;
import client.net.sf.saxon.ce.expr.XPathContextMajor;
import client.net.sf.saxon.ce.expr.instruct.*;
import client.net.sf.saxon.ce.functions.Component;
import client.net.sf.saxon.ce.functions.EscapeURI;
import client.net.sf.saxon.ce.js.IXSLFunction;
import client.net.sf.saxon.ce.lib.ErrorListener;
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.*;
import client.net.sf.saxon.ce.trans.CompilerInfo;
import client.net.sf.saxon.ce.trans.Mode;
import client.net.sf.saxon.ce.trans.RuleManager;
import client.net.sf.saxon.ce.trans.XPathException;
import client.net.sf.saxon.ce.trans.update.PendingUpdateList;
import client.net.sf.saxon.ce.tree.iter.SingletonIterator;
import client.net.sf.saxon.ce.tree.linked.LinkedTreeBuilder;
import client.net.sf.saxon.ce.value.DateTimeValue;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Node;
import com.google.gwt.logging.client.LogConfiguration;
import com.google.gwt.user.client.Event;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
/**
* The Controller is equivalent to Saxon-HE's implementation of the same name, and represents
* an executing instance of a transformation or query. Multiple concurrent executions of
* the same transformation or query will use different Controller instances. This class is
* therefore not thread-safe.
* <p>
* The Controller is serially reusable, when one transformation or query
* is finished, it can be used to run another. However, there is no advantage in doing this
* rather than allocating a new Controller each time. An inert version of the controller can
* be used to simply hold state for the benefit of the JavaScript API, controller settings
* can then be copied to a 'live' Controller instance using importControllerSettings()
* <p>
* A dummy Controller is created by the JavaScript API for holding settings.
* <p>
* The Controller holds those parts of the dynamic context that do not vary during the course
* of a transformation or query, or that do not change once their value has been computed.
* This also includes those parts of the static context that are required at run-time.
* <p>
*
* @author Michael H. Kay
* @since 8.4
*/
public class Controller {
public Controller() {}
private Configuration config;
private Item initialContextItem;
private Item contextForGlobalVariables;
private Bindery bindery; // holds values of global and local variables
private NamePool namePool;
private RuleManager ruleManager;
private HashMap<StructuredQName, ValueRepresentation> parameters;
private PreparedStylesheet preparedStylesheet;
private String principalResultURI;
private String cookedPrincipalResultURI;
//private boolean thereHasBeenAnExplicitResultDocument;
private ErrorListener errorListener;
private int recoveryPolicy;
private Executable executable;
private Template initialTemplate = null;
private HashSet<DocumentURI> allOutputDestinations;
private HashMap<DocumentURI, Node> resultDocumentPool;
private SequenceOutputter reusableSequenceOutputter = null;
private HashMap<String, Object> userDataTable = new HashMap<String, Object>(20);
private DateTimeValue currentDateTime;
private boolean dateTimePreset = false;
private StructuredQName initialMode = null;
private NodeInfo lastRememberedNode = null;
private int lastRememberedNumber = -1;
private boolean inUse = false;
private boolean stripSourceTrees = true;
//private HashMap<String, Document> resultTrees = new HashMap<String, Document>();
private PendingUpdateList pendingUpdateList;
private String initialTemplateName = null;
private boolean isInert;
private Node targetNode;
private APIcommand commandType;
private static ArrayList<Xslt20ProcessorImpl> eventProcessors= null;
private static ArrayList<Xslt20ProcessorImpl> nonDomEventProcessors= null;
private HTMLWriter openHTMLWriter = null;
private Node principalOutputNode = null;
private NodeInfo sourceNode = null;
/**
* Create a Controller and initialise variables. Note: XSLT applications should
* create the Controller by using the JAXP newTransformer() method, or in S9API
* by using XsltExecutable.load()
*
* @param config The Configuration used by this Controller
*/
public static void addEventProcessor(Xslt20ProcessorImpl proc) {
eventProcessors = addProcessor(eventProcessors, proc);
}
public static void relayEvent(Node node, Event event) {
if (eventProcessors != null){
for (Xslt20ProcessorImpl p: eventProcessors){
if (p != null) {
p.bubbleApplyTemplates(node, event);
}
}
}
}
public static ArrayList<Xslt20ProcessorImpl> addProcessor(ArrayList<Xslt20ProcessorImpl> list, Xslt20ProcessorImpl proc){
if (list == null) {
list = new ArrayList<Xslt20ProcessorImpl>();
}
list.add(proc);
return list;
}
public static void addNonDomEventProcessor(Xslt20ProcessorImpl proc) {
nonDomEventProcessors = addProcessor(nonDomEventProcessors, proc);
}
public static void relayNonDomEvent(String name, JavaScriptObject target, JavaScriptObject event) {
if (nonDomEventProcessors != null){
for (Xslt20ProcessorImpl p: nonDomEventProcessors){
if (p != null) {
StructuredQName sqn = new StructuredQName("", NamespaceConstant.IXSL, name);
// context was set to host page
p.applyEventTemplates(sqn.getClarkName(), null, event, target);
}
}
}
}
public Controller(Configuration config, boolean isInert) {
this.isInert = isInert;
this.config = config;
// create a dummy executable
executable = new Executable(config);
reset();
}
public Controller(Configuration config) {
isInert = false;
this.config = config;
// create a dummy executable
executable = new Executable(config);
reset();
}
public enum APIcommand {
UPDATE_HTML, TRANSFORM_TO_DOCUMENT, TRANSFORM_TO_FRAGMENT, TRANSFORM_TO_HTML_FRAGMENT, NONE
}
/**
* Create a Controller and initialise variables.
*
* @param config The Configuration used by this Controller
* @param executable The executable used by this Controller
*/
public Controller(Configuration config, Executable executable) {
isInert = false;
this.config = config;
this.executable = executable;
this.errorListener = config.getErrorListener();
reset();
}
/**
* <p>Reset this <code>Transformer</code> to its original configuration.</p>
* <p/>
* <p><code>Transformer</code> is reset to the same state as when it was created with
* {@link javax.xml.transform.TransformerFactory#newTransformer()},
* {@link javax.xml.transform.TransformerFactory#newTransformer(javax.xml.transform.Source source)} or
* {@link javax.xml.transform.Templates#newTransformer()}.
* <code>reset()</code> is designed to allow the reuse of existing <code>Transformer</code>s
* thus saving resources associated with the creation of new <code>Transformer</code>s.</p>
* <p>
* <p>The reset <code>Transformer</code> is not guaranteed to have the same {@link javax.xml.transform.URIResolver}
* or {@link javax.xml.transform.ErrorListener} <code>Object</code>s, e.g. {@link Object#equals(Object obj)}.
* It is guaranteed to have a functionally equal <code>URIResolver</code>
* and <code>ErrorListener</code>.</p>
*
* @since 1.5
*/
public void reset() {
bindery = new Bindery();
namePool = config.getNamePool();
recoveryPolicy = config.getRecoveryPolicy();
if (errorListener instanceof StandardErrorListener) {
// if using a standard error listener, make a fresh one
// for each transformation, because it is stateful - and also because the
// host language is now known (a Configuration can serve multiple host languages)
PrintStream ps = ((StandardErrorListener)errorListener).getErrorOutput();
errorListener = ((StandardErrorListener)errorListener).makeAnother();
((StandardErrorListener)errorListener).setErrorOutput(ps);
((StandardErrorListener)errorListener).setRecoveryPolicy(recoveryPolicy);
}
contextForGlobalVariables = null;
parameters = null;
currentDateTime = null;
dateTimePreset = false;
initialContextItem = null;
initialMode = null;
initialTemplate = null;
initialTemplateName = null;
clearPerTransformationData();
pendingUpdateList = new PendingUpdateList(config);
targetNode = null;
commandType = APIcommand.NONE;
resultDocumentPool = null;
openHTMLWriter = null;
}
public void importControllerSettings(Controller lc) throws XPathException {
this.setBaseOutputURI(lc.getBaseOutputURI());
this.setInitialMode(lc.getInitialModeName());
this.setInitialTemplate(lc.getInitialTemplateName());
this.setParameters(lc.getParameters()); // shared reference only
this.setBaseOutputURI(lc.getBaseOutputURI());
this.setTargetNode(lc.getTargetNode());
this.setApiCommand(lc.getApiCommand());
this.setSourceNode(lc.getSourceNode());
}
/**
* Reset variables that need to be reset for each transformation if the controller
* is serially reused
*/
private void clearPerTransformationData() {
//userDataTable = new HashMap<String, Object>(20);
//principalResult = null;
//principalResultURI = null;
allOutputDestinations = null;
resultDocumentPool = null;
//thereHasBeenAnExplicitResultDocument = false;
lastRememberedNode = null;
lastRememberedNumber = -1;
openHTMLWriter = null;
}
/**
* Get the Configuration associated with this Controller. The Configuration holds
* settings that potentially apply globally to many different queries and transformations.
* @return the Configuration object
* @since 8.4
*/
public Configuration getConfiguration() {
return config;
}
public void setTargetNode(Node target) {
targetNode = target;
}
public Node getTargetNode() {
return targetNode;
}
public void setSourceNode(NodeInfo source) {
sourceNode = source;
}
public NodeInfo getSourceNode() {
return sourceNode;
}
public void setApiCommand(APIcommand command) {
commandType = command;
}
public APIcommand getApiCommand() {
return commandType;
}
/**
* Set the initial mode for the transformation.
* <p>
* XSLT 2.0 allows a transformation to be started in a mode other than the default mode.
* The transformation then starts by looking for the template rule in this mode that best
* matches the initial context node.
* <p>
* This method may eventually be superseded by a standard JAXP method.
*
* @param expandedModeName the name of the initial mode. The mode is
* supplied as an expanded QName, that is "localname" if there is no
* namespace, or "{uri}localname" otherwise. If the value is null or zero-length,
* the initial mode is reset to the unnamed default mode.
* @since 8.4
*/
public void setInitialMode(String expandedModeName) {
if (expandedModeName == null || expandedModeName.length() == 0) {
initialMode = null;
} else {
initialMode = StructuredQName.fromClarkName(expandedModeName);
}
}
/**
* Get the initial mode for the transformation
* @return the initial mode, as a name in Clark format
*/
public String getInitialMode() {
if (initialMode == null) {
return null;
} else {
return initialMode.getClarkName();
}
}
public String getInitialModeName() {
if (initialMode == null) {
return null;
}
return initialMode.getDisplayName();
}
public HashMap<StructuredQName, ValueRepresentation> getParameters() {
return parameters;
}
public void setParameters(HashMap<StructuredQName, ValueRepresentation> params) {
parameters = params;
}
public String getInitialTemplateName(){
return initialTemplateName;
}
/**
* Set the base output URI.
*
* <p>This defaults to the system ID of the Result object for the principal output
* of the transformation if this is known; if it is not known, it defaults
* to the current directory.</p>
*
* <p> The base output URI is used for resolving relative URIs in the <code>href</code> attribute
* of the <code>xsl:result-document</code> instruction.</p>
*
* @param uri the base output URI
* @since 8.4
*/
public void setBaseOutputURI(String uri) {
principalResultURI = uri;
}
/**
* Get the base output URI.
*
* <p>This returns the value set using the {@link #setBaseOutputURI} method. If no value has been set
* explicitly, then the method returns null if called before the transformation, or the computed
* default base output URI if called after the transformation.</p>
*
* <p> The base output URI is used for resolving relative URIs in the <code>href</code> attribute
* of the <code>xsl:result-document</code> instruction.</p>
*
* @return the base output URI
* @since 8.4
*/
public String getBaseOutputURI() {
return principalResultURI;
}
/**
* Get the base output URI after processing. The processing consists of (a) defaulting
* to the current user directory if no base URI is available and if the stylesheet is trusted,
* and (b) applying IRI-to-URI escaping
* @return the base output URI after processing.
*/
public String getCookedBaseOutputURI() {
if (cookedPrincipalResultURI == null) {
String base = getBaseOutputURI();
if (base != null) {
base = EscapeURI.iriToUri(base).toString();
}
cookedPrincipalResultURI = base;
}
return cookedPrincipalResultURI;
}
/**
* Check that an output destination has not been used before, optionally adding
* this URI to the set of URIs that have been used.
* @param uri the URI to be used as the output destination
* @return true if the URI is available for use; false if it has already been used.
* <p>
* This method is intended for internal use only.
*/
public boolean checkUniqueOutputDestination(DocumentURI uri) {
if (uri == null) {
return true; // happens when writing say to an anonymous StringWriter
}
if (allOutputDestinations == null) {
allOutputDestinations = new HashSet<DocumentURI>(20);
}
return !(allOutputDestinations.contains(uri));
}
/**
* Add a URI to the set of output destinations that cannot be written to, either because
* they have already been written to, or because they have been read
* @param uri A URI that is not available as an output destination
*/
public void addUnavailableOutputDestination(DocumentURI uri) {
if (allOutputDestinations == null) {
allOutputDestinations = new HashSet<DocumentURI>(20);
}
allOutputDestinations.add(uri);
}
public void addToResultDocumentPool(DocumentURI uri, Node doc) {
addUnavailableOutputDestination(uri);
if (resultDocumentPool == null) {
resultDocumentPool = new HashMap<DocumentURI,Node>(20);
}
resultDocumentPool.put(uri, doc);
}
public int getResultDocumentCount() {
return (resultDocumentPool == null)? 0 :resultDocumentPool.size();
}
public void importResults(Controller ctrl){
this.resultDocumentPool = ctrl.resultDocumentPool;
this.principalOutputNode = ctrl.principalOutputNode; // bugfix
}
public Node getResultDocument(String uri) {
if (uri == null || uri.length() == 0) {
return principalOutputNode;
}
DocumentURI docURI = new DocumentURI(uri);
if (resultDocumentPool == null) {
return null;
} else if (resultDocumentPool.containsKey(docURI)) {
return resultDocumentPool.get(docURI);
} else {
return null;
}
}
private JavaScriptObject getJsResultSet() {
JavaScriptObject docArray = IXSLFunction.jsArray(resultDocumentPool.size());
int poolSize = resultDocumentPool.size();
Node[] nodes = new Node[poolSize];
nodes = resultDocumentPool.values().toArray(nodes);
poolSize--;
for (int i = 0; i <= poolSize; i++) {
IXSLFunction.jsSetArrayItem(docArray, poolSize -i, nodes[i]);
}
return docArray;
}
private JavaScriptObject getJsResultURIset() {
JavaScriptObject uriArray = IXSLFunction.jsArray(resultDocumentPool.size());
int poolSize = resultDocumentPool.size();
DocumentURI[] uris = new DocumentURI[poolSize];
uris = resultDocumentPool.keySet().toArray(uris);
poolSize--;
for (int i = 0; i <= poolSize; i++) {
IXSLFunction.jsSetArrayItem(uriArray, poolSize - i, uris[i].toString());
}
return uriArray;
}
/**
* For JavaScriptAPI
* @return a JavaScript object with a property for each result document.
* The property name is the URI and the property value the document object
*/
public JavaScriptObject getResultDocURIArray() {
if (resultDocumentPool == null) {
return IXSLFunction.jsArray(0);
} else {
return getJsResultURIset();
}
}
/**
* Returns a JavaScript object with a property for each result document.
* The property name is the URI and the property value the document object
* @param keys A JavaScript array of result document keys (URIs)
* @param values A JavaScript array of result document values (DOM objects)
* @return A JavaScript object with similar functionality to a HashMap.
*/
private static native JavaScriptObject createArray(JavaScriptObject keys) /*-{
var obj = new Array(keys.length);
for (var i = 0; i < keys.length; i++) {
obj[i] = keys[i];
}
return obj;
}-*/;
/**
* Check whether an XSLT implicit result tree can be written. This is allowed only if no xsl:result-document
* has been written for the principal output URI
*/
public void checkImplicitResultTree() throws XPathException {
if (principalResultURI != null && !checkUniqueOutputDestination(new DocumentURI(principalResultURI))) {
XPathException err = new XPathException(
"Cannot write an implicit result document if an explicit result document has been written to the same URI: " +
principalResultURI);
err.setErrorCode("XTDE1490");
throw err;
}
}
/**
* Set that an explicit result tree has been written using xsl:result-document
*/
public void setThereHasBeenAnExplicitResultDocument() {
//thereHasBeenAnExplicitResultDocument = true;
}
/**
* Test whether an explicit result tree has been written using xsl:result-document
* @return true if the transformation has evaluated an xsl:result-document instruction
*/
public boolean hasThereBeenAnExplicitResultDocument() {
return (resultDocumentPool != null && resultDocumentPool.size() > 0);
//return thereHasBeenAnExplicitResultDocument;
}
/**
* Allocate a SequenceOutputter for a new output destination. Reuse the existing one
* if it is available for reuse (this is designed to ensure that the TinyTree structure
* is also reused, creating a forest of trees all sharing the same data structure)
* @param size the estimated size of the output sequence
* @return SequenceOutputter the allocated SequenceOutputter
*/
public SequenceOutputter allocateSequenceOutputter(int size) {
if (reusableSequenceOutputter != null) {
SequenceOutputter out = reusableSequenceOutputter;
out.setSystemId(null); // Added 10.8.2009 - seems right, but doesn't solve EvaluateNodeTest problem
reusableSequenceOutputter = null;
return out;
} else {
return new SequenceOutputter(this, size);
}
}
/**
* Accept a SequenceOutputter that is now available for reuse
* @param out the SequenceOutputter that is available for reuse
*/
public void reuseSequenceOutputter(SequenceOutputter out) {
reusableSequenceOutputter = out;
}
/**
* Get the pending update list
* @return the pending update list
*/
public PendingUpdateList getPendingUpdateList() {
return pendingUpdateList;
}
///////////////////////////////////////////////////////////////////////////////
/**
* Set the initial named template to be used as the entry point.
* <p>
* XSLT 2.0 allows a transformation to start by executing a named template, rather than
* by matching an initial context node in a source document. This method may eventually
* be superseded by a standard JAXP method once JAXP supports XSLT 2.0.
* <p>
* Note that any parameters supplied using {@link #setParameter} are used as the values
* of global stylesheet parameters. There is no way to supply values for local parameters
* of the initial template.
*
* @param expandedName The expanded name of the template in {uri}local format, or null
* or a zero-length string to indicate that there should be no initial template.
* @throws XPathException if there is no named template with this name
* @since 8.4
*/
public void setInitialTemplate(String expandedName) throws XPathException {
if (expandedName == null || expandedName.length() == 0) {
initialTemplate = null;
initialTemplateName = null;
return;
}
initialTemplateName = expandedName;
if (isInert) {
return;
}
StructuredQName qName = StructuredQName.fromClarkName(expandedName);
Template t = ((PreparedStylesheet)getExecutable()).getNamedTemplate(qName);
if (t == null) {
XPathException err = new XPathException("The requested initial template, with expanded name "
+ expandedName + ", does not exist");
err.setErrorCode("XTDE0040");
reportFatalError(err);
throw err;
} else if (t.hasRequiredParams()) {
XPathException err = new XPathException("The named template "
+ expandedName
+ " has required parameters, so cannot be used as the entry point");
err.setErrorCode("XTDE0060");
reportFatalError(err);
throw err;
} else {
initialTemplate = t;
}
}
/**
* Get the initial template
* @return the name of the initial template, as an expanded name in Clark format if set, or null otherwise
* @since 8.7
*/
public String getInitialTemplate() {
if (initialTemplate == null) {
return null;
} else {
return initialTemplate.getTemplateName().getClarkName();
}
}
///////////////////////////////////////////////////////////////////////////////
/**
* Make a PipelineConfiguration based on the properties of this Controller.
* <p>
* This interface is intended primarily for internal use, although it may be necessary
* for applications to call it directly if they construct pull or push pipelines
* @return a newly constructed PipelineConfiguration holding a reference to this
* Controller as well as other configuration information.
*/
public PipelineConfiguration makePipelineConfiguration() {
PipelineConfiguration pipe = new PipelineConfiguration();
pipe.setConfiguration(getConfiguration());
pipe.setErrorListener(getErrorListener());
pipe.setController(this);
return pipe;
}
/**
* Set the policy for handling recoverable XSLT errors.
*
* <p>Since 9.3 this call has no effect unless the error listener in use is a {@link StandardErrorListener}
* or a subclass thereof. Calling this method then results in a call to the StandardErrorListener
* to set the recovery policy, and the action that is taken on calls of the various methods
* error(), fatalError(), and warning() is then the responsibility of the ErrorListener itself.</p>
*
* <p>Since 9.2 the policy for handling the most common recoverable error, namely the ambiguous template
* match that arises when a node matches more than one match pattern, is a compile-time rather than run-time
* setting, and can be controlled using {@link CompilerInfo#setRecoveryPolicy(int)} </p>
*
* @param policy the recovery policy to be used. The options are {@link Configuration#RECOVER_SILENTLY},
* {@link Configuration#RECOVER_WITH_WARNINGS}, or {@link Configuration#DO_NOT_RECOVER}.
* @since 8.7.1
*/
public void setRecoveryPolicy(int policy) {
recoveryPolicy = policy;
if (errorListener instanceof StandardErrorListener) {
((StandardErrorListener)errorListener).setRecoveryPolicy(policy);
}
}
/**
* Get the policy for handling recoverable errors
*
* @return the current policy. If none has been set with this Controller, the value registered with the
* Configuration is returned.
* @since 8.7.1
*/
public int getRecoveryPolicy() {
return recoveryPolicy;
}
/**
* Set the error listener.
*
* @param listener the ErrorListener to be used
*/
public void setErrorListener(ErrorListener listener) {
errorListener = listener;
}
/**
* Get the error listener.
*
* @return the ErrorListener in use
*/
public ErrorListener getErrorListener() {
return errorListener;
}
/**
* Report a recoverable error. This is an XSLT concept: by default, such an error results in a warning
* message, and processing continues. In XQuery, however, there are no recoverable errors so a fatal
* error is reported.
* <p>
* This method is intended for internal use only.
*
* @param err An exception holding information about the error
* @throws XPathException if the error listener decides not to
* recover from the error
*/
public void recoverableError(XPathException err) throws XPathException {
getConfiguration().issueWarning(err.getMessage());
}
/**
* Report a fatal error
* @param err the error to be reported
*/
public void reportFatalError(XPathException err) {
if (!err.hasBeenReported()) {
getErrorListener().error(err);
err.setHasBeenReported(true);
}
}
/////////////////////////////////////////////////////////////////////////////////////////
// Methods for managing the various runtime control objects
/////////////////////////////////////////////////////////////////////////////////////////
/**
* Get the Executable object.
* <p>
* This method is intended for internal use only.
*
* @return the Executable (which represents the compiled stylesheet)
*/
public Executable getExecutable() {
return executable;
}
/**
* Get the document pool. This is used only for source documents, not for stylesheet modules.
* <p>
* This method is intended for internal use only.
*
* @return the source document pool
*/
public DocumentPool getDocumentPool() {
return getConfiguration().getDocumentPool();
}
/**
* Set the initial context item, when running XSLT invoked with a named template.
* <p/>
* When a transformation is invoked using the {@link #transform} method, the
* initial context node is set automatically. This method is useful in XQuery,
* to define an initial context node for evaluating global variables, and also
* in XSLT 2.0, when the transformation is started by invoking a named template.
*
* <p>When an initial context item is set, it also becomes the context item used for
* evaluating global variables. The two context items can only be different when the
* {@link #transform} method is used to transform a document starting at a node other
* than the root.</p>
*
* <p>In XQuery, the two context items are always
* the same; in XSLT, the context node for evaluating global variables is the root of the
* tree containing the initial context item.</p>
*
* @param item The initial context item.
* @since 8.7
*/
public void setInitialContextItem(Item item) {
initialContextItem = item;
contextForGlobalVariables = item;
}
/**
* Get the current bindery.
* <p>
* This method is intended for internal use only.
*
* @return the Bindery (in which values of all variables are held)
*/
public Bindery getBindery() {
return bindery;
}
/**
* Get the initial context item. This returns the item (often a document node)
* previously supplied to the {@link #setInitialContextItem} method, or the
* initial context node set implicitly using methods such as {@link #transform}.
* @return the initial context item. Note that in XSLT this must be a node, but in
* XQuery it may also be an atomic value.
* @since 8.7
*/
public Item getInitialContextItem() {
return initialContextItem;
}
/**
* Get the item used as the context for evaluating global variables. In XQuery this
* is the same as the initial context item; in XSLT it is the root of the tree containing
* the initial context node.
* @return the context item for evaluating global variables, or null if there is none
* @since 8.7
*/
public Item getContextForGlobalVariables() {
return contextForGlobalVariables;
// See bug 5224, which points out that the rules for XQuery 1.0 weren't clearly defined
}
/**
* Get the name pool in use. The name pool is responsible for mapping QNames used in source
* documents and compiled stylesheets and queries into numeric codes. All source documents
* used by a given transformation or query must use the same name pool as the compiled stylesheet
* or query.
*
* @return the name pool in use
* @since 8.4
*/
public NamePool getNamePool() {
return namePool;
}
/**
* Make a builder for the selected tree model.
*
* @return an instance of the Builder for the chosen tree model
* @since 8.4
*/
public Builder makeBuilder() {
return new LinkedTreeBuilder();
}
/**
* Say whether the transformation should perform whitespace stripping as defined
* by the xsl:strip-space and xsl:preserve-space declarations in the stylesheet
* in the case where a source tree is supplied to the transformation as a tree
* (typically a DOMSource, or a Saxon NodeInfo).
* The default is true. It is legitimate to suppress whitespace
* stripping if the client knows that all unnecessary whitespace has already been removed
* from the tree before it is processed. Note that this option applies to all source
* documents for which whitespace-stripping is normally applied, that is, both the
* principal source documents, and documents read using the doc(), document(), and
* collection() functions. It does not apply to source documents that are supplied
* in the form of a SAXSource or StreamSource, for which whitespace is stripped
* during the process of tree construction.
* <p>Generally, stripping whitespace speeds up the transformation if it is done
* while building the source tree, but slows it down if it is applied to a tree that
* has already been built. So if the same source tree is used as input to a number
* of transformations, it is better to strip the whitespace once at the time of
* tree construction, rather than doing it on-the-fly during each transformation.</p>
* @param strip true if whitespace is to be stripped from supplied source trees
* as defined by xsl:strip-space; false to suppress whitespace stripping
* @since 9.3
*/
public void setStripSourceTrees(boolean strip) {
stripSourceTrees = strip;
}
/**
* Ask whether the transformation will perform whitespace stripping for supplied source trees as defined
* by the xsl:strip-space and xsl:preserve-space declarations in the stylesheet.
* @return true unless whitespace stripping has been suppressed using
* {@link #setStripSourceTrees(boolean)}.
* @since 9.3
*/
public boolean isStripSourceTree() {
return stripSourceTrees;
}
/**
* Add a document to the document pool, and check that it is suitable for use in this query or
* transformation. This check rejects the document if document has been validated (and thus carries
* type annotations) but the query or transformation is not schema-aware.
* <p>
* This method is intended for internal use only.
*
* @param doc the root node of the document to be added. Must not be null.
* @param uri the document-URI property of this document. If non-null, the document is registered
* in the document pool with this as its document URI.
*/
public void registerDocument(DocumentInfo doc, DocumentURI uri) throws XPathException {
if (doc == null) {
throw new NullPointerException("null");
}
if (uri != null) {
getConfiguration().getDocumentPool().add(doc, uri);
}
}
////////////////////////////////////////////////////////////////////////////////
// Methods for registering and retrieving handlers for template rules
////////////////////////////////////////////////////////////////////////////////
/**
* Set the RuleManager, used to manage template rules for each mode.
* <p>
* This method is intended for internal use only.
*
* @param r the Rule Manager
*/
public void setRuleManager(RuleManager r) {
ruleManager = r;
}
/**
* Get the Rule Manager.
* <p>
* This method is intended for internal use only.
*
* @return the Rule Manager, used to hold details of template rules for
* all modes
*/
public RuleManager getRuleManager() {
return ruleManager;
}
/**
* Associate this Controller with a compiled stylesheet.
* <p>
* This method is intended for internal use only.
*
* @param sheet the compiled stylesheet
*/
public void setPreparedStylesheet(PreparedStylesheet sheet) {
preparedStylesheet = sheet;
executable = sheet.getExecutable();
recoveryPolicy = sheet.getCompilerInfo().getRecoveryPolicy();
}
public PreparedStylesheet getPreparedStylesheet(){
return preparedStylesheet;
}
/**
* Associate this Controller with an Executable. This method is used by the XQuery
* processor. The Executable object is overkill in this case - the only thing it
* currently holds are copies of the collation table.
* <p>
* This method is intended for internal use only
* @param exec the Executable
*/
public void setExecutable(Executable exec) {
executable = exec;
}
/**
* Initialize the controller ready for a new transformation. This method should not normally be called by
* users (it is done automatically when transform() is invoked). However, it is available as a low-level API
* especially for use with XQuery.
*/
private void initializeController() throws XPathException {
if (preparedStylesheet != null) {
setRuleManager(preparedStylesheet.getRuleManager());
}
//setDecimalFormatManager(executable.getDecimalFormatManager());
// get a new bindery, to clear out any variables from previous runs
bindery = new Bindery();
executable.initializeBindery(bindery);
// if parameters were supplied, set them up
defineGlobalParameters();
}
/**
* Register the global parameters of the transformation or query. This should be called after a sequence
* of calls on {@link #setParameter}. It checks that all required parameters have been supplied, and places
* the values of the parameters in the Bindery to make them available for use during the query or
* transformation.
* <p>
* This method is intended for internal use only
*/
public void defineGlobalParameters() throws XPathException {
executable.checkAllRequiredParamsArePresent(parameters);
bindery.defineGlobalParameters(parameters);
}
/////////////////////////////////////////////////////////////////////////
// Allow user data to be associated with nodes on a tree
/////////////////////////////////////////////////////////////////////////
/**
* Get user data associated with a key. To retrieve user data, two objects are required:
* an arbitrary object that may be regarded as the container of the data (originally, and
* typically still, a node in a tree), and a name. The name serves to distingush data objects
* associated with the same node by different client applications.
* <p>
* This method is intended primarily for internal use, though it may also be
* used by advanced applications.
*
* @param key an object acting as a key for this user data value. This must be equal
* (in the sense of the equals() method) to the key supplied when the data value was
* registered using {@link #setUserData}.
* @param name the name of the required property
* @return the value of the required property
*/
public Object getUserData(Object key, String name) {
String keyValue = key.hashCode() + " " + name;
// System.err.println("getUserData " + name + " on object returning " + userDataTable.get(key));
return userDataTable.get(keyValue);
}
/**
* Set user data associated with a key. To store user data, two objects are required:
* an arbitrary object that may be regarded as the container of the data (originally, and
* typically still, a node in a tree), and a name. The name serves to distingush data objects
* associated with the same node by different client applications.
* <p>
* This method is intended primarily for internal use, though it may also be
* used by advanced applications.
*
* @param key an object acting as a key for this user data value. This can be any object, for example
* a node or a string. If data for the given object and name already exists, it is overwritten.
* @param name the name of the required property
* @param data the value of the required property. If null is supplied, any existing entry
* for the key is removed.
*/
public void setUserData(Object key, String name, Object data) {
// System.err.println("setUserData " + name + " on object to " + data);
String keyVal = key.hashCode() + " " + name;
if (data==null) {
userDataTable.remove(keyVal);
} else {
userDataTable.put(keyVal, data);
}
}
/////////////////////////////////////////////////////////////////////////
// implement the javax.xml.transform.Transformer methods
/////////////////////////////////////////////////////////////////////////
/**
* Perform a transformation from a Source document to a Result document.
*
* @exception XPathException if the transformation fails. As a
* special case, the method throws a TerminationException (a subclass
* of XPathException) if the transformation was terminated using
* xsl:message terminate="yes".
* @param source The input for the source tree. May be null if and only if an
* initial template has been supplied.
* @return The root of the result tree.
*/
public Node transform(NodeInfo source, com.google.gwt.dom.client.Node target) throws Exception {
if (inUse) {
throw new IllegalStateException(
"The Transformer is being used recursively or concurrently. This is not permitted.");
}
clearPerTransformationData();
if (preparedStylesheet==null) {
throw new XPathException("Stylesheet has not been prepared");
}
if (!dateTimePreset) {
currentDateTime = null; // reset at start of each transformation
}
// no longer used for expiry check - just XSLT context
getCurrentDateTime();
if (LogConfiguration.loggingIsEnabled()) {
LogController.openTraceListener();
}
boolean success = false;
try {
if (source == null) {
if (initialTemplate == null) {
throw new XPathException("Either a source document or an initial template must be specified");
}
} else {
Mode mode = preparedStylesheet.getRuleManager().getMode(initialMode, false);
if (mode == null || (initialMode != null && mode.isEmpty())) {
throw new XPathException("Requested initial mode " +
(initialMode == null ? "" : initialMode.getDisplayName()) +
" does not exist", "XTDE0045");
}
if (source.getSystemId() != null) {
registerDocument(source.getDocumentRoot(), new DocumentURI(source.getSystemId()));
}
}
// System.err.println("*** TransformDocument");
if (executable==null) {
throw new XPathException("Stylesheet has not been compiled");
}
openMessageEmitter();
XPathContextMajor initialContext = newXPathContext();
if (source != null) {
initialContextItem = source;
contextForGlobalVariables = source.getRoot();
SequenceIterator currentIter = SingletonIterator.makeIterator(source);
if (initialTemplate != null) {
currentIter.next();
}
initialContext.setCurrentIterator(currentIter);
}
initializeController();
PipelineConfiguration pipe = makePipelineConfiguration();
Receiver result = openResult(pipe, initialContext, target, ResultDocument.APPEND_CONTENT);
// Process the source document by applying template rules to the initial context node
if (initialTemplate == null) {
initialContextItem = source;
Mode mode = getRuleManager().getMode(initialMode, false);
if (mode == null || (initialMode != null && mode.isEmpty())) {
throw new XPathException("Requested initial mode " +
(initialMode == null ? "" : initialMode.getDisplayName()) +
" does not exist", "XTDE0045");
}
TailCall tc = ApplyTemplates.applyTemplates(
initialContext.getCurrentIterator(),
mode,
null, null, initialContext, null);
while (tc != null) {
tc = tc.processLeavingTail();
}
} else {
Template t = initialTemplate;
XPathContextMajor c2 = initialContext.newContext();
c2.openStackFrame(t.getStackFrameMap());
c2.setLocalParameters(new ParameterSet());
c2.setTunnelParameters(new ParameterSet());
TailCall tc = t.expand(c2);
while (tc != null) {
tc = tc.processLeavingTail();
}
}
closeMessageEmitter();
// the principalURI doesn't have significance because the output is a
// standalone DOM object - unlike result-documents that are included in the
// resultdocument pool, therefore don't check the URI:
//checkPrincipalURI(result, initialContext);
closeResult(result, initialContext);
pendingUpdateList.apply(initialContext);
success = true;
principalOutputNode = openHTMLWriter.getNode();
return principalOutputNode;
// let caller handle exception
} finally {
inUse = false;
principalResultURI = null;
if (LogConfiguration.loggingIsEnabled()) {
LogController.closeTraceListener(success);
}
}
}
private void closeMessageEmitter() throws XPathException {
//getMessageEmitter().close();
}
public void closeResult(Receiver result, XPathContext initialContext) throws XPathException {
Receiver out = initialContext.getReceiver();
out.endDocument();
out.close();
}
private void checkPrincipalURI(Receiver result, XPathContext initialContext) throws XPathException {
Receiver out = initialContext.getReceiver();
if (out instanceof ComplexContentOutputter && ((ComplexContentOutputter)out).contentHasBeenWritten()) {
if (principalResultURI != null) {
DocumentURI documentKey = new DocumentURI(principalResultURI);
if (!checkUniqueOutputDestination(documentKey)) {
XPathException err = new XPathException(
"Cannot write more than one result document to the same URI, or write to a URI that has been read: " +
documentKey);
err.setErrorCode("XTDE1490");
throw err;
} else {
addUnavailableOutputDestination(documentKey);
}
}
}
}
public Receiver openResult(PipelineConfiguration pipe, XPathContext initialContext,
Node root, int method) throws XPathException {
// if (method == ResultDocument.REPLACE_CONTENT) {
// while (true) {
// Node child = root.getFirstChild();
// if (child == null) {
// break;
// }
// root.removeChild(child);
// }
// }
HTMLWriter writer = new HTMLWriter();
writer.setPipelineConfiguration(pipe);
NamespaceReducer reducer = new NamespaceReducer();
reducer.setUnderlyingReceiver(writer);
reducer.setPipelineConfiguration(pipe);
writer.setNode(root);
Receiver receiver = reducer;
// if this is the implicit XSLT result document, and if the executable is capable
// of creating a secondary result document, then add a filter to check the first write
boolean openNow = false;
if (getExecutable().createsSecondaryResult()) {
receiver = new ImplicitResultChecker(receiver, this);
receiver.setPipelineConfiguration(pipe);
} else {
openNow = true;
}
initialContext.changeOutputDestination(receiver, true);
if (openNow) {
Receiver out = initialContext.getReceiver();
out.open();
out.startDocument();
}
openHTMLWriter = writer;
return receiver;
}
private void openMessageEmitter() throws XPathException {
// if (getMessageEmitter() == null) {
// Receiver me = makeMessageReceiver();
// setMessageEmitter(me);
// }
// getMessageEmitter().open();
}
//////////////////////////////////////////////////////////////////////////
// Handle parameters to the transformation
//////////////////////////////////////////////////////////////////////////
/**
* Supply a parameter using Saxon-specific representations of the name and value
* @param qName The structured representation of the parameter name
* @param value The value of the parameter, or null to remove a previously set value
*/
public void setParameter(StructuredQName qName, ValueRepresentation value) {
if (parameters == null) {
parameters = new HashMap<StructuredQName, ValueRepresentation>();
}
parameters.put(qName, value);
}
public void removeParameter(StructuredQName qName) {
if (parameters != null) {
parameters.remove(qName);
}
}
/**
* Reset the parameters to a null list.
*/
public void clearParameters() {
parameters = null;
}
/**
* Get a parameter to the transformation. This returns the value of a parameter
* that has been previously set using the {@link #setParameter} method. The value
* is returned exactly as supplied, that is, before any conversion to an XPath value.
*
* @param qName the name of the required parameter
* @return the value of the parameter, if it exists, or null otherwise
*/
public ValueRepresentation getParameter(StructuredQName qName) {
if (parameters==null) {
return null;
}
return parameters.get(qName);
}
/**
* Set the current date and time for this query or transformation.
* This method is provided primarily for testing purposes, to allow tests to be run with
* a fixed date and time. The supplied date/time must include a timezone, which is used
* as the implicit timezone.
*
* <p>Note that comparisons of date/time values currently use the implicit timezone
* taken from the system clock, not from the value supplied here.</p>
*
* @param dateTime the date/time value to be used as the current date and time
* @throws IllegalStateException if a current date/time has already been
* established by calling getCurrentDateTime(), or by a previous call on setCurrentDateTime()
*/
public void setCurrentDateTime(DateTimeValue dateTime) throws XPathException {
if (currentDateTime==null) {
if (dateTime.getComponent(Component.TIMEZONE) == null) {
throw new XPathException("No timezone is present in supplied value of current date/time");
}
currentDateTime = dateTime;
dateTimePreset = true;
} else {
throw new IllegalStateException(
"Current date and time can only be set once, and cannot subsequently be changed");
}
}
/**
* Get the current date and time for this query or transformation.
* All calls during one transformation return the same answer.
*
* @return Get the current date and time. This will deliver the same value
* for repeated calls within the same transformation
*/
public DateTimeValue getCurrentDateTime() {
if (currentDateTime==null) {
try {
currentDateTime = DateTimeValue.fromJavaDate(new Date());
} catch (XPathException err) {
throw new IllegalStateException(err);
}
}
return currentDateTime;
}
/**
* Get the implicit timezone for this query or transformation
* @return the implicit timezone as an offset in minutes
*/
public int getImplicitTimezone() {
return getCurrentDateTime().getTimezoneInMinutes();
}
/////////////////////////////////////////
// Methods for handling dynamic context
/////////////////////////////////////////
/**
* Make an XPathContext object for expression evaluation.
* <p>
* This method is intended for internal use.
*
* @return the new XPathContext
*/
public XPathContextMajor newXPathContext() {
return new XPathContextMajor(this);
}
/**
* Set the last remembered node, for node numbering purposes.
* <p>
* This method is strictly for internal use only.
*
* @param node the node in question
* @param number the number of this node
*/
public void setRememberedNumber(NodeInfo node, int number) {
lastRememberedNode = node;
lastRememberedNumber = number;
}
/**
* Get the number of a node if it is the last remembered one.
* <p>
* This method is strictly for internal use only.
*
* @param node the node for which remembered information is required
* @return the number of this node if known, else -1.
*/
public int getRememberedNumber(NodeInfo node) {
if (lastRememberedNode == node) {
return lastRememberedNumber;
}
return -1;
}
/**
* Get a result tree, given its URI
* @param uri the URI of the result tree
*/
// public Document getResultTree(String uri) {
// return resultTrees.get(uri);
// }
/**
* Set a result tree (internal method)
* @param uri the URI of the result tree
* @param doc the document node of the result tree
*/
// public void setResultTree(String uri, Document doc) {
// resultTrees.put(uri, doc);
// }
//
// /**
// * Get the set of all result tree URIs
// */
//
// public Set<String> getResultTreeUris() {
// return resultTrees.keySet();
// }
/////////////////////////////////////////////////////////////////////////
// Methods for tracing
/////////////////////////////////////////////////////////////////////////
/**
* Set a TraceListener, replacing any existing TraceListener
* <p>This method has no effect unless the stylesheet or query was compiled
* with tracing enabled.</p>
*
* @param listener the TraceListener to be set. May be null, in which case
* trace events will not be reported
* @since 9.2
*/
private TraceListener traceListener;
public void setTraceListener(TraceListener listener) {
this.traceListener = listener;
}
/**
* Get the TraceListener. By default, there is no TraceListener, and this
* method returns null. A TraceListener may be added using the method
* {@link #addTraceListener}. If more than one TraceListener has been added,
* this method will return a composite TraceListener. Because the form
* this takes is implementation-dependent, this method is not part of the
* stable Saxon public API.
*
* @return the TraceListener used for XSLT or XQuery instruction tracing, or null if absent.
*/
/*@Nullable*/
public TraceListener getTraceListener() { // e.g.
return traceListener;
}
/**
* Test whether instruction execution is being traced. This will be true
* if (a) at least one TraceListener has been registered using the
* {@link #addTraceListener} method, and (b) tracing has not been temporarily
* paused using the {@link #pauseTracing} method.
*
* @return true if tracing is active, false otherwise
* @since 8.4
*/
public final boolean isTracing() { // e.g.
return traceListener != null && !tracingPaused;
}
/**
* Pause or resume tracing. While tracing is paused, trace events are not sent to any
* of the registered TraceListeners.
*
* @param pause true if tracing is to pause; false if it is to resume
* @since 8.4
*/
private boolean tracingPaused = false;
public final void pauseTracing(boolean pause) {
tracingPaused = pause;
}
/**
* Adds the specified trace listener to receive trace events from
* this instance. Note that although TraceListeners can be added
* or removed dynamically, this has no effect unless the stylesheet
* or query has been compiled with tracing enabled. This is achieved
* by calling {@link Configuration#setTraceListener} or by setting
* the attribute {@link net.sf.saxon.lib.FeatureKeys#TRACE_LISTENER} on the
* TransformerFactory. Conversely, if this property has been set in the
* Configuration or TransformerFactory, the TraceListener will automatically
* be added to every Controller that uses that Configuration.
*
* @param trace the trace listener. If null is supplied, the call has no effect.
* @since 8.4
*/
public void addTraceListener(/*@Nullable*/ TraceListener trace) { // e.g.
if (trace != null) {
//traceListener = TraceEventMulticaster.add(traceListener, trace);
}
}
/**
* Removes the specified trace listener so that the listener will no longer
* receive trace events.
*
* @param trace the trace listener.
* @since 8.4
*/
public void removeTraceListener(TraceListener trace) { // e.g.
//traceListener = TraceEventMulticaster.remove(traceListener, trace);
}
//////////////////////
public void setOutputProperties(Object props) {
// TODO Auto-generated method stub - did use java.util.Properties
}
}
// 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. Portions marked "e.g." are from Edwin Glaser (edwin@pannenleiter.de)
//