Package client.net.sf.saxon.ce

Source Code of client.net.sf.saxon.ce.Controller

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)
//
TOP

Related Classes of client.net.sf.saxon.ce.Controller

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.