| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| /* |
| * $Id$ |
| */ |
| package org.apache.xpath; |
| |
| import java.lang.reflect.Method; |
| import java.util.Stack; |
| import java.util.Vector; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| |
| import javax.xml.transform.ErrorListener; |
| import javax.xml.transform.SourceLocator; |
| import javax.xml.transform.TransformerException; |
| import javax.xml.transform.URIResolver; |
| |
| import org.apache.xalan.extensions.ExpressionContext; |
| import org.apache.xalan.res.XSLMessages; |
| import org.apache.xml.dtm.Axis; |
| import org.apache.xml.dtm.DTM; |
| import org.apache.xml.dtm.DTMFilter; |
| import org.apache.xml.dtm.DTMIterator; |
| import org.apache.xml.dtm.DTMManager; |
| import org.apache.xml.dtm.DTMWSFilter; |
| import org.apache.xml.dtm.ref.sax2dtm.SAX2RTFDTM; |
| import org.apache.xml.utils.IntStack; |
| import org.apache.xml.utils.NodeVector; |
| import org.apache.xml.utils.ObjectStack; |
| import org.apache.xml.utils.PrefixResolver; |
| import org.apache.xml.utils.SAXSourceLocator; |
| import org.apache.xml.utils.XMLString; |
| import org.apache.xpath.axes.SubContextList; |
| import org.apache.xpath.objects.XObject; |
| import org.apache.xpath.objects.DTMXRTreeFrag; |
| import org.apache.xpath.objects.XString; |
| import org.apache.xpath.res.XPATHErrorResources; |
| |
| import org.xml.sax.XMLReader; |
| |
| /** |
| * Default class for the runtime execution context for XPath. |
| * |
| * <p>This class extends DTMManager but does not directly implement it.</p> |
| * @xsl.usage advanced |
| */ |
| public class XPathContext extends DTMManager // implements ExpressionContext |
| { |
| IntStack m_last_pushed_rtfdtm=new IntStack(); |
| /** |
| * Stack of cached "reusable" DTMs for Result Tree Fragments. |
| * This is a kluge to handle the problem of starting an RTF before |
| * the old one is complete. |
| * |
| * %REVIEW% I'm using a Vector rather than Stack so we can reuse |
| * the DTMs if the problem occurs multiple times. I'm not sure that's |
| * really a net win versus discarding the DTM and starting a new one... |
| * but the retained RTF DTM will have been tail-pruned so should be small. |
| */ |
| private Vector m_rtfdtm_stack=null; |
| /** Index of currently active RTF DTM in m_rtfdtm_stack */ |
| private int m_which_rtfdtm=-1; |
| |
| /** |
| * Most recent "reusable" DTM for Global Result Tree Fragments. No stack is |
| * required since we're never going to pop these. |
| */ |
| private SAX2RTFDTM m_global_rtfdtm=null; |
| |
| /** |
| * HashMap of cached the DTMXRTreeFrag objects, which are identified by DTM IDs. |
| * The object are just wrappers for DTMs which are used in XRTreeFrag. |
| */ |
| private HashMap m_DTMXRTreeFrags = null; |
| |
| /** |
| * state of the secure processing feature. |
| */ |
| private boolean m_isSecureProcessing = false; |
| |
| /** |
| * Though XPathContext context extends |
| * the DTMManager, it really is a proxy for this object, which |
| * is the real DTMManager. |
| */ |
| protected DTMManager m_dtmManager = DTMManager.newInstance( |
| org.apache.xpath.objects.XMLStringFactoryImpl.getFactory()); |
| |
| /** |
| * Return the DTMManager object. Though XPathContext context extends |
| * the DTMManager, it really is a proxy for the real DTMManager. If a |
| * caller needs to make a lot of calls to the DTMManager, it is faster |
| * if it gets the real one from this function. |
| */ |
| public DTMManager getDTMManager() |
| { |
| return m_dtmManager; |
| } |
| |
| /** |
| * Set the state of the secure processing feature |
| */ |
| public void setSecureProcessing(boolean flag) |
| { |
| m_isSecureProcessing = flag; |
| } |
| |
| /** |
| * Return the state of the secure processing feature |
| */ |
| public boolean isSecureProcessing() |
| { |
| return m_isSecureProcessing; |
| } |
| |
| /** |
| * Get an instance of a DTM, loaded with the content from the |
| * specified source. If the unique flag is true, a new instance will |
| * always be returned. Otherwise it is up to the DTMManager to return a |
| * new instance or an instance that it already created and may be being used |
| * by someone else. |
| * (I think more parameters will need to be added for error handling, and entity |
| * resolution). |
| * |
| * @param source the specification of the source object, which may be null, |
| * in which case it is assumed that node construction will take |
| * by some other means. |
| * @param unique true if the returned DTM must be unique, probably because it |
| * is going to be mutated. |
| * @param wsfilter Enables filtering of whitespace nodes, and may be null. |
| * @param incremental true if the construction should try and be incremental. |
| * @param doIndexing true if the caller considers it worth it to use |
| * indexing schemes. |
| * |
| * @return a non-null DTM reference. |
| */ |
| public DTM getDTM(javax.xml.transform.Source source, boolean unique, |
| DTMWSFilter wsfilter, |
| boolean incremental, |
| boolean doIndexing) |
| { |
| return m_dtmManager.getDTM(source, unique, wsfilter, |
| incremental, doIndexing); |
| } |
| |
| /** |
| * Get an instance of a DTM that "owns" a node handle. |
| * |
| * @param nodeHandle the nodeHandle. |
| * |
| * @return a non-null DTM reference. |
| */ |
| public DTM getDTM(int nodeHandle) |
| { |
| return m_dtmManager.getDTM(nodeHandle); |
| } |
| |
| /** |
| * Given a W3C DOM node, try and return a DTM handle. |
| * Note: calling this may be non-optimal. |
| * |
| * @param node Non-null reference to a DOM node. |
| * |
| * @return a valid DTM handle. |
| */ |
| public int getDTMHandleFromNode(org.w3c.dom.Node node) |
| { |
| return m_dtmManager.getDTMHandleFromNode(node); |
| } |
| // |
| // |
| /** |
| * %TBD% Doc |
| */ |
| public int getDTMIdentity(DTM dtm) |
| { |
| return m_dtmManager.getDTMIdentity(dtm); |
| } |
| // |
| /** |
| * Creates an empty <code>DocumentFragment</code> object. |
| * @return A new <code>DocumentFragment handle</code>. |
| */ |
| public DTM createDocumentFragment() |
| { |
| return m_dtmManager.createDocumentFragment(); |
| } |
| // |
| /** |
| * Release a DTM either to a lru pool, or completely remove reference. |
| * DTMs without system IDs are always hard deleted. |
| * State: experimental. |
| * |
| * @param dtm The DTM to be released. |
| * @param shouldHardDelete True if the DTM should be removed no matter what. |
| * @return true if the DTM was removed, false if it was put back in a lru pool. |
| */ |
| public boolean release(DTM dtm, boolean shouldHardDelete) |
| { |
| // %REVIEW% If it's a DTM which may contain multiple Result Tree |
| // Fragments, we can't discard it unless we know not only that it |
| // is empty, but that the XPathContext itself is going away. So do |
| // _not_ accept the request. (May want to do it as part of |
| // reset(), though.) |
| if(m_rtfdtm_stack!=null && m_rtfdtm_stack.contains(dtm)) |
| { |
| return false; |
| } |
| |
| return m_dtmManager.release(dtm, shouldHardDelete); |
| } |
| |
| /** |
| * Create a new <code>DTMIterator</code> based on an XPath |
| * <a href="http://www.w3.org/TR/xpath#NT-LocationPath>LocationPath</a> or |
| * a <a href="http://www.w3.org/TR/xpath#NT-UnionExpr">UnionExpr</a>. |
| * |
| * @param xpathCompiler ??? Somehow we need to pass in a subpart of the |
| * expression. I hate to do this with strings, since the larger expression |
| * has already been parsed. |
| * |
| * @param pos The position in the expression. |
| * @return The newly created <code>DTMIterator</code>. |
| */ |
| public DTMIterator createDTMIterator(Object xpathCompiler, int pos) |
| { |
| return m_dtmManager.createDTMIterator(xpathCompiler, pos); |
| } |
| // |
| /** |
| * Create a new <code>DTMIterator</code> based on an XPath |
| * <a href="http://www.w3.org/TR/xpath#NT-LocationPath>LocationPath</a> or |
| * a <a href="http://www.w3.org/TR/xpath#NT-UnionExpr">UnionExpr</a>. |
| * |
| * @param xpathString Must be a valid string expressing a |
| * <a href="http://www.w3.org/TR/xpath#NT-LocationPath>LocationPath</a> or |
| * a <a href="http://www.w3.org/TR/xpath#NT-UnionExpr">UnionExpr</a>. |
| * |
| * @param presolver An object that can resolve prefixes to namespace URLs. |
| * |
| * @return The newly created <code>DTMIterator</code>. |
| */ |
| public DTMIterator createDTMIterator(String xpathString, |
| PrefixResolver presolver) |
| { |
| return m_dtmManager.createDTMIterator(xpathString, presolver); |
| } |
| // |
| /** |
| * Create a new <code>DTMIterator</code> based only on a whatToShow and |
| * a DTMFilter. The traversal semantics are defined as the descendant |
| * access. |
| * |
| * @param whatToShow This flag specifies which node types may appear in |
| * the logical view of the tree presented by the iterator. See the |
| * description of <code>NodeFilter</code> for the set of possible |
| * <code>SHOW_</code> values.These flags can be combined using |
| * <code>OR</code>. |
| * @param filter The <code>NodeFilter</code> to be used with this |
| * <code>TreeWalker</code>, or <code>null</code> to indicate no filter. |
| * @param entityReferenceExpansion The value of this flag determines |
| * whether entity reference nodes are expanded. |
| * |
| * @return The newly created <code>NodeIterator</code>. |
| */ |
| public DTMIterator createDTMIterator(int whatToShow, |
| DTMFilter filter, boolean entityReferenceExpansion) |
| { |
| return m_dtmManager.createDTMIterator(whatToShow, filter, entityReferenceExpansion); |
| } |
| |
| /** |
| * Create a new <code>DTMIterator</code> that holds exactly one node. |
| * |
| * @param node The node handle that the DTMIterator will iterate to. |
| * |
| * @return The newly created <code>DTMIterator</code>. |
| */ |
| public DTMIterator createDTMIterator(int node) |
| { |
| // DescendantIterator iter = new DescendantIterator(); |
| DTMIterator iter = new org.apache.xpath.axes.OneStepIteratorForward(Axis.SELF); |
| iter.setRoot(node, this); |
| return iter; |
| // return m_dtmManager.createDTMIterator(node); |
| } |
| |
| /** |
| * Create an XPathContext instance. This is equivalent to calling |
| * the {@link #XPathContext(boolean)} constructor with the value |
| * <code>true</code>. |
| */ |
| public XPathContext() { |
| this(true); |
| } |
| |
| /** |
| * Create an XPathContext instance. |
| * @param recursiveVarContext A <code>boolean</code> value indicating whether |
| * the XPath context needs to support pushing of scopes for |
| * variable resolution |
| */ |
| public XPathContext(boolean recursiveVarContext) { |
| m_prefixResolvers.push(null); |
| m_currentNodes.push(DTM.NULL); |
| m_currentExpressionNodes.push(DTM.NULL); |
| m_saxLocations.push(null); |
| m_variableStacks = recursiveVarContext ? new VariableStack() |
| : new VariableStack(1); |
| } |
| |
| /** |
| * Create an XPathContext instance. This is equivalent to calling the |
| * constructor {@link #XPathContext(java.lang.Object,boolean)} with the |
| * value of the second parameter set to <code>true</code>. |
| * @param owner Value that can be retrieved via the getOwnerObject() method. |
| * @see #getOwnerObject |
| */ |
| public XPathContext(Object owner) |
| { |
| this(owner, true); |
| } |
| |
| /** |
| * Create an XPathContext instance. |
| * @param owner Value that can be retrieved via the getOwnerObject() method. |
| * @see #getOwnerObject |
| * @param recursiveVarContext A <code>boolean</code> value indicating whether |
| * the XPath context needs to support pushing of scopes for |
| * variable resolution |
| */ |
| public XPathContext(Object owner, boolean recursiveVarContext) { |
| this(recursiveVarContext); |
| m_owner = owner; |
| try { |
| m_ownerGetErrorListener = m_owner.getClass().getMethod("getErrorListener", new Class[] {}); |
| } |
| catch (NoSuchMethodException nsme) {} |
| } |
| |
| /** |
| * Reset for new run. |
| */ |
| public void reset() |
| { |
| releaseDTMXRTreeFrags(); |
| // These couldn't be disposed of earlier (see comments in release()); zap them now. |
| if(m_rtfdtm_stack!=null) |
| for (java.util.Enumeration e = m_rtfdtm_stack.elements() ; e.hasMoreElements() ;) |
| m_dtmManager.release((DTM)e.nextElement(), true); |
| |
| m_rtfdtm_stack=null; // drop our references too |
| m_which_rtfdtm=-1; |
| |
| if(m_global_rtfdtm!=null) |
| m_dtmManager.release(m_global_rtfdtm,true); |
| m_global_rtfdtm=null; |
| |
| |
| m_dtmManager = DTMManager.newInstance( |
| org.apache.xpath.objects.XMLStringFactoryImpl.getFactory()); |
| |
| m_saxLocations.removeAllElements(); |
| m_axesIteratorStack.removeAllElements(); |
| m_contextNodeLists.removeAllElements(); |
| m_currentExpressionNodes.removeAllElements(); |
| m_currentNodes.removeAllElements(); |
| m_iteratorRoots.RemoveAllNoClear(); |
| m_predicatePos.removeAllElements(); |
| m_predicateRoots.RemoveAllNoClear(); |
| m_prefixResolvers.removeAllElements(); |
| |
| m_prefixResolvers.push(null); |
| m_currentNodes.push(DTM.NULL); |
| m_currentExpressionNodes.push(DTM.NULL); |
| m_saxLocations.push(null); |
| } |
| |
| /** The current stylesheet locator. */ |
| ObjectStack m_saxLocations = new ObjectStack(RECURSIONLIMIT); |
| |
| /** |
| * Set the current locater in the stylesheet. |
| * |
| * @param location The location within the stylesheet. |
| */ |
| public void setSAXLocator(SourceLocator location) |
| { |
| m_saxLocations.setTop(location); |
| } |
| |
| /** |
| * Set the current locater in the stylesheet. |
| * |
| * @param location The location within the stylesheet. |
| */ |
| public void pushSAXLocator(SourceLocator location) |
| { |
| m_saxLocations.push(location); |
| } |
| |
| /** |
| * Push a slot on the locations stack so that setSAXLocator can be |
| * repeatedly called. |
| * |
| */ |
| public void pushSAXLocatorNull() |
| { |
| m_saxLocations.push(null); |
| } |
| |
| |
| /** |
| * Pop the current locater. |
| */ |
| public void popSAXLocator() |
| { |
| m_saxLocations.pop(); |
| } |
| |
| /** |
| * Get the current locater in the stylesheet. |
| * |
| * @return The location within the stylesheet, or null if not known. |
| */ |
| public SourceLocator getSAXLocator() |
| { |
| return (SourceLocator) m_saxLocations.peek(); |
| } |
| |
| /** The owner context of this XPathContext. In the case of XSLT, this will be a |
| * Transformer object. |
| */ |
| private Object m_owner; |
| |
| /** The owner context of this XPathContext. In the case of XSLT, this will be a |
| * Transformer object. |
| */ |
| private Method m_ownerGetErrorListener; |
| |
| /** |
| * Get the "owner" context of this context, which should be, |
| * in the case of XSLT, the Transformer object. This is needed |
| * so that XSLT functions can get the Transformer. |
| * @return The owner object passed into the constructor, or null. |
| */ |
| public Object getOwnerObject() |
| { |
| return m_owner; |
| } |
| |
| // ================ VarStack =================== |
| |
| /** |
| * The stack of Variable stacks. A VariableStack will be |
| * pushed onto this stack for each template invocation. |
| */ |
| private VariableStack m_variableStacks; |
| |
| /** |
| * Get the variable stack, which is in charge of variables and |
| * parameters. |
| * |
| * @return the variable stack, which should not be null. |
| */ |
| public final VariableStack getVarStack() |
| { |
| return m_variableStacks; |
| } |
| |
| /** |
| * Get the variable stack, which is in charge of variables and |
| * parameters. |
| * |
| * @param varStack non-null reference to the variable stack. |
| */ |
| public final void setVarStack(VariableStack varStack) |
| { |
| m_variableStacks = varStack; |
| } |
| |
| // ================ SourceTreeManager =================== |
| |
| /** The source tree manager, which associates Source objects to source |
| * tree nodes. */ |
| private SourceTreeManager m_sourceTreeManager = new SourceTreeManager(); |
| |
| /** |
| * Get the SourceTreeManager associated with this execution context. |
| * |
| * @return the SourceTreeManager associated with this execution context. |
| */ |
| public final SourceTreeManager getSourceTreeManager() |
| { |
| return m_sourceTreeManager; |
| } |
| |
| /** |
| * Set the SourceTreeManager associated with this execution context. |
| * |
| * @param mgr the SourceTreeManager to be associated with this |
| * execution context. |
| */ |
| public void setSourceTreeManager(SourceTreeManager mgr) |
| { |
| m_sourceTreeManager = mgr; |
| } |
| |
| // ================================================= |
| |
| /** The ErrorListener where errors and warnings are to be reported. */ |
| private ErrorListener m_errorListener; |
| |
| /** A default ErrorListener in case our m_errorListener was not specified and our |
| * owner either does not have an ErrorListener or has a null one. |
| */ |
| private ErrorListener m_defaultErrorListener; |
| |
| /** |
| * Get the ErrorListener where errors and warnings are to be reported. |
| * |
| * @return A non-null ErrorListener reference. |
| */ |
| public final ErrorListener getErrorListener() |
| { |
| |
| if (null != m_errorListener) |
| return m_errorListener; |
| |
| ErrorListener retval = null; |
| |
| try { |
| if (null != m_ownerGetErrorListener) |
| retval = (ErrorListener) m_ownerGetErrorListener.invoke(m_owner, new Object[] {}); |
| } |
| catch (Exception e) {} |
| |
| if (null == retval) |
| { |
| if (null == m_defaultErrorListener) |
| m_defaultErrorListener = new org.apache.xml.utils.DefaultErrorHandler(); |
| retval = m_defaultErrorListener; |
| } |
| |
| return retval; |
| } |
| |
| /** |
| * Set the ErrorListener where errors and warnings are to be reported. |
| * |
| * @param listener A non-null ErrorListener reference. |
| */ |
| public void setErrorListener(ErrorListener listener) throws IllegalArgumentException |
| { |
| if (listener == null) |
| throw new IllegalArgumentException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NULL_ERROR_HANDLER, null)); //"Null error handler"); |
| m_errorListener = listener; |
| } |
| |
| |
| // ================================================= |
| |
| /** The TrAX URI Resolver for resolving URIs from the document(...) |
| * function to source tree nodes. */ |
| private URIResolver m_uriResolver; |
| |
| /** |
| * Get the URIResolver associated with this execution context. |
| * |
| * @return a URI resolver, which may be null. |
| */ |
| public final URIResolver getURIResolver() |
| { |
| return m_uriResolver; |
| } |
| |
| /** |
| * Set the URIResolver associated with this execution context. |
| * |
| * @param resolver the URIResolver to be associated with this |
| * execution context, may be null to clear an already set resolver. |
| */ |
| public void setURIResolver(URIResolver resolver) |
| { |
| m_uriResolver = resolver; |
| } |
| |
| // ================================================= |
| |
| /** The reader of the primary source tree. */ |
| public XMLReader m_primaryReader; |
| |
| /** |
| * Get primary XMLReader associated with this execution context. |
| * |
| * @return The reader of the primary source tree. |
| */ |
| public final XMLReader getPrimaryReader() |
| { |
| return m_primaryReader; |
| } |
| |
| /** |
| * Set primary XMLReader associated with this execution context. |
| * |
| * @param reader The reader of the primary source tree. |
| */ |
| public void setPrimaryReader(XMLReader reader) |
| { |
| m_primaryReader = reader; |
| } |
| |
| // ================================================= |
| |
| |
| /** Misnamed string manager for XPath messages. */ |
| // private static XSLMessages m_XSLMessages = new XSLMessages(); |
| |
| /** |
| * Tell the user of an assertion error, and probably throw an |
| * exception. |
| * |
| * @param b If false, a TransformerException will be thrown. |
| * @param msg The assertion message, which should be informative. |
| * |
| * @throws javax.xml.transform.TransformerException if b is false. |
| */ |
| private void assertion(boolean b, String msg) throws javax.xml.transform.TransformerException |
| { |
| if (!b) |
| { |
| ErrorListener errorHandler = getErrorListener(); |
| |
| if (errorHandler != null) |
| { |
| errorHandler.fatalError( |
| new TransformerException( |
| XSLMessages.createMessage( |
| XPATHErrorResources.ER_INCORRECT_PROGRAMMER_ASSERTION, |
| new Object[]{ msg }), (SAXSourceLocator)this.getSAXLocator())); |
| } |
| } |
| } |
| |
| //========================================================== |
| // SECTION: Execution context state tracking |
| //========================================================== |
| |
| /** |
| * The current context node list. |
| */ |
| private Stack m_contextNodeLists = new Stack(); |
| |
| public Stack getContextNodeListsStack() { return m_contextNodeLists; } |
| public void setContextNodeListsStack(Stack s) { m_contextNodeLists = s; } |
| |
| /** |
| * Get the current context node list. |
| * |
| * @return the <a href="http://www.w3.org/TR/xslt#dt-current-node-list">current node list</a>, |
| * also refered to here as a <term>context node list</term>. |
| */ |
| public final DTMIterator getContextNodeList() |
| { |
| |
| if (m_contextNodeLists.size() > 0) |
| return (DTMIterator) m_contextNodeLists.peek(); |
| else |
| return null; |
| } |
| |
| /** |
| * Set the current context node list. |
| * |
| * @param nl the <a href="http://www.w3.org/TR/xslt#dt-current-node-list">current node list</a>, |
| * also refered to here as a <term>context node list</term>. |
| * @xsl.usage internal |
| */ |
| public final void pushContextNodeList(DTMIterator nl) |
| { |
| m_contextNodeLists.push(nl); |
| } |
| |
| /** |
| * Pop the current context node list. |
| * @xsl.usage internal |
| */ |
| public final void popContextNodeList() |
| { |
| if(m_contextNodeLists.isEmpty()) |
| System.err.println("Warning: popContextNodeList when stack is empty!"); |
| else |
| m_contextNodeLists.pop(); |
| } |
| |
| /** |
| * The ammount to use for stacks that record information during the |
| * recursive execution. |
| */ |
| public static final int RECURSIONLIMIT = (1024*4); |
| |
| /** The stack of <a href="http://www.w3.org/TR/xslt#dt-current-node">current node</a> objects. |
| * Not to be confused with the current node list. %REVIEW% Note that there |
| * are no bounds check and resize for this stack, so if it is blown, it's all |
| * over. */ |
| private IntStack m_currentNodes = new IntStack(RECURSIONLIMIT); |
| |
| // private NodeVector m_currentNodes = new NodeVector(); |
| |
| public IntStack getCurrentNodeStack() {return m_currentNodes; } |
| public void setCurrentNodeStack(IntStack nv) { m_currentNodes = nv; } |
| |
| /** |
| * Get the current context node. |
| * |
| * @return the <a href="http://www.w3.org/TR/xslt#dt-current-node">current node</a>. |
| */ |
| public final int getCurrentNode() |
| { |
| return m_currentNodes.peek(); |
| } |
| |
| /** |
| * Set the current context node and expression node. |
| * |
| * @param cn the <a href="http://www.w3.org/TR/xslt#dt-current-node">current node</a>. |
| * @param en the sub-expression context node. |
| */ |
| public final void pushCurrentNodeAndExpression(int cn, int en) |
| { |
| m_currentNodes.push(cn); |
| m_currentExpressionNodes.push(cn); |
| } |
| |
| /** |
| * Set the current context node. |
| */ |
| public final void popCurrentNodeAndExpression() |
| { |
| m_currentNodes.quickPop(1); |
| m_currentExpressionNodes.quickPop(1); |
| } |
| |
| /** |
| * Push the current context node, expression node, and prefix resolver. |
| * |
| * @param cn the <a href="http://www.w3.org/TR/xslt#dt-current-node">current node</a>. |
| * @param en the sub-expression context node. |
| * @param nc the namespace context (prefix resolver. |
| */ |
| public final void pushExpressionState(int cn, int en, PrefixResolver nc) |
| { |
| m_currentNodes.push(cn); |
| m_currentExpressionNodes.push(cn); |
| m_prefixResolvers.push(nc); |
| } |
| |
| /** |
| * Pop the current context node, expression node, and prefix resolver. |
| */ |
| public final void popExpressionState() |
| { |
| m_currentNodes.quickPop(1); |
| m_currentExpressionNodes.quickPop(1); |
| m_prefixResolvers.pop(); |
| } |
| |
| |
| |
| /** |
| * Set the current context node. |
| * |
| * @param n the <a href="http://www.w3.org/TR/xslt#dt-current-node">current node</a>. |
| */ |
| public final void pushCurrentNode(int n) |
| { |
| m_currentNodes.push(n); |
| } |
| |
| /** |
| * Pop the current context node. |
| */ |
| public final void popCurrentNode() |
| { |
| m_currentNodes.quickPop(1); |
| } |
| |
| /** |
| * Set the current predicate root. |
| */ |
| public final void pushPredicateRoot(int n) |
| { |
| m_predicateRoots.push(n); |
| } |
| |
| /** |
| * Pop the current predicate root. |
| */ |
| public final void popPredicateRoot() |
| { |
| m_predicateRoots.popQuick(); |
| } |
| |
| /** |
| * Get the current predicate root. |
| */ |
| public final int getPredicateRoot() |
| { |
| return m_predicateRoots.peepOrNull(); |
| } |
| |
| /** |
| * Set the current location path iterator root. |
| */ |
| public final void pushIteratorRoot(int n) |
| { |
| m_iteratorRoots.push(n); |
| } |
| |
| /** |
| * Pop the current location path iterator root. |
| */ |
| public final void popIteratorRoot() |
| { |
| m_iteratorRoots.popQuick(); |
| } |
| |
| /** |
| * Get the current location path iterator root. |
| */ |
| public final int getIteratorRoot() |
| { |
| return m_iteratorRoots.peepOrNull(); |
| } |
| |
| /** A stack of the current sub-expression nodes. */ |
| private NodeVector m_iteratorRoots = new NodeVector(); |
| |
| /** A stack of the current sub-expression nodes. */ |
| private NodeVector m_predicateRoots = new NodeVector(); |
| |
| /** A stack of the current sub-expression nodes. */ |
| private IntStack m_currentExpressionNodes = new IntStack(RECURSIONLIMIT); |
| |
| |
| public IntStack getCurrentExpressionNodeStack() { return m_currentExpressionNodes; } |
| public void setCurrentExpressionNodeStack(IntStack nv) { m_currentExpressionNodes = nv; } |
| |
| private IntStack m_predicatePos = new IntStack(); |
| |
| public final int getPredicatePos() |
| { |
| return m_predicatePos.peek(); |
| } |
| |
| public final void pushPredicatePos(int n) |
| { |
| m_predicatePos.push(n); |
| } |
| |
| public final void popPredicatePos() |
| { |
| m_predicatePos.pop(); |
| } |
| |
| /** |
| * Get the current node that is the expression's context (i.e. for current() support). |
| * |
| * @return The current sub-expression node. |
| */ |
| public final int getCurrentExpressionNode() |
| { |
| return m_currentExpressionNodes.peek(); |
| } |
| |
| /** |
| * Set the current node that is the expression's context (i.e. for current() support). |
| * |
| * @param n The sub-expression node to be current. |
| */ |
| public final void pushCurrentExpressionNode(int n) |
| { |
| m_currentExpressionNodes.push(n); |
| } |
| |
| /** |
| * Pop the current node that is the expression's context |
| * (i.e. for current() support). |
| */ |
| public final void popCurrentExpressionNode() |
| { |
| m_currentExpressionNodes.quickPop(1); |
| } |
| |
| private ObjectStack m_prefixResolvers |
| = new ObjectStack(RECURSIONLIMIT); |
| |
| /** |
| * Get the current namespace context for the xpath. |
| * |
| * @return the current prefix resolver for resolving prefixes to |
| * namespace URLs. |
| */ |
| public final PrefixResolver getNamespaceContext() |
| { |
| return (PrefixResolver) m_prefixResolvers.peek(); |
| } |
| |
| /** |
| * Get the current namespace context for the xpath. |
| * |
| * @param pr the prefix resolver to be used for resolving prefixes to |
| * namespace URLs. |
| */ |
| public final void setNamespaceContext(PrefixResolver pr) |
| { |
| m_prefixResolvers.setTop(pr); |
| } |
| |
| /** |
| * Push a current namespace context for the xpath. |
| * |
| * @param pr the prefix resolver to be used for resolving prefixes to |
| * namespace URLs. |
| */ |
| public final void pushNamespaceContext(PrefixResolver pr) |
| { |
| m_prefixResolvers.push(pr); |
| } |
| |
| /** |
| * Just increment the namespace contest stack, so that setNamespaceContext |
| * can be used on the slot. |
| */ |
| public final void pushNamespaceContextNull() |
| { |
| m_prefixResolvers.push(null); |
| } |
| |
| /** |
| * Pop the current namespace context for the xpath. |
| */ |
| public final void popNamespaceContext() |
| { |
| m_prefixResolvers.pop(); |
| } |
| |
| //========================================================== |
| // SECTION: Current TreeWalker contexts (for internal use) |
| //========================================================== |
| |
| /** |
| * Stack of AxesIterators. |
| */ |
| private Stack m_axesIteratorStack = new Stack(); |
| |
| public Stack getAxesIteratorStackStacks() { return m_axesIteratorStack; } |
| public void setAxesIteratorStackStacks(Stack s) { m_axesIteratorStack = s; } |
| |
| /** |
| * Push a TreeWalker on the stack. |
| * |
| * @param iter A sub-context AxesWalker. |
| * @xsl.usage internal |
| */ |
| public final void pushSubContextList(SubContextList iter) |
| { |
| m_axesIteratorStack.push(iter); |
| } |
| |
| /** |
| * Pop the last pushed axes iterator. |
| * @xsl.usage internal |
| */ |
| public final void popSubContextList() |
| { |
| m_axesIteratorStack.pop(); |
| } |
| |
| /** |
| * Get the current axes iterator, or return null if none. |
| * |
| * @return the sub-context node list. |
| * @xsl.usage internal |
| */ |
| public SubContextList getSubContextList() |
| { |
| return m_axesIteratorStack.isEmpty() |
| ? null : (SubContextList) m_axesIteratorStack.peek(); |
| } |
| |
| /** |
| * Get the <a href="http://www.w3.org/TR/xslt#dt-current-node-list">current node list</a> |
| * as defined by the XSLT spec. |
| * |
| * @return the <a href="http://www.w3.org/TR/xslt#dt-current-node-list">current node list</a>. |
| * @xsl.usage internal |
| */ |
| |
| public org.apache.xpath.axes.SubContextList getCurrentNodeList() |
| { |
| return m_axesIteratorStack.isEmpty() |
| ? null : (SubContextList) m_axesIteratorStack.elementAt(0); |
| } |
| //========================================================== |
| // SECTION: Implementation of ExpressionContext interface |
| //========================================================== |
| |
| /** |
| * Get the current context node. |
| * @return The current context node. |
| */ |
| public final int getContextNode() |
| { |
| return this.getCurrentNode(); |
| } |
| |
| /** |
| * Get the current context node list. |
| * @return An iterator for the current context list, as |
| * defined in XSLT. |
| */ |
| public final DTMIterator getContextNodes() |
| { |
| |
| try |
| { |
| DTMIterator cnl = getContextNodeList(); |
| |
| if (null != cnl) |
| return cnl.cloneWithReset(); |
| else |
| return null; // for now... this might ought to be an empty iterator. |
| } |
| catch (CloneNotSupportedException cnse) |
| { |
| return null; // error reporting? |
| } |
| } |
| |
| XPathExpressionContext expressionContext = new XPathExpressionContext(); |
| |
| /** |
| * The the expression context for extensions for this context. |
| * |
| * @return An object that implements the ExpressionContext. |
| */ |
| public ExpressionContext getExpressionContext() |
| { |
| return expressionContext; |
| } |
| |
| public class XPathExpressionContext implements ExpressionContext |
| { |
| /** |
| * Return the XPathContext associated with this XPathExpressionContext. |
| * Extensions should use this judiciously and only when special processing |
| * requirements cannot be met another way. Consider requesting an enhancement |
| * to the ExpressionContext interface to avoid having to call this method. |
| * @return the XPathContext associated with this XPathExpressionContext. |
| */ |
| public XPathContext getXPathContext() |
| { |
| return XPathContext.this; |
| } |
| |
| /** |
| * Return the DTMManager object. Though XPathContext context extends |
| * the DTMManager, it really is a proxy for the real DTMManager. If a |
| * caller needs to make a lot of calls to the DTMManager, it is faster |
| * if it gets the real one from this function. |
| */ |
| public DTMManager getDTMManager() |
| { |
| return m_dtmManager; |
| } |
| |
| /** |
| * Get the current context node. |
| * @return The current context node. |
| */ |
| public org.w3c.dom.Node getContextNode() |
| { |
| int context = getCurrentNode(); |
| |
| return getDTM(context).getNode(context); |
| } |
| |
| /** |
| * Get the current context node list. |
| * @return An iterator for the current context list, as |
| * defined in XSLT. |
| */ |
| public org.w3c.dom.traversal.NodeIterator getContextNodes() |
| { |
| return new org.apache.xml.dtm.ref.DTMNodeIterator(getContextNodeList()); |
| } |
| |
| /** |
| * Get the error listener. |
| * @return The registered error listener. |
| */ |
| public ErrorListener getErrorListener() |
| { |
| return XPathContext.this.getErrorListener(); |
| } |
| |
| /** |
| * Get the value of a node as a number. |
| * @param n Node to be converted to a number. May be null. |
| * @return value of n as a number. |
| */ |
| public double toNumber(org.w3c.dom.Node n) |
| { |
| // %REVIEW% You can't get much uglier than this... |
| int nodeHandle = getDTMHandleFromNode(n); |
| DTM dtm = getDTM(nodeHandle); |
| XString xobj = (XString)dtm.getStringValue(nodeHandle); |
| return xobj.num(); |
| } |
| |
| /** |
| * Get the value of a node as a string. |
| * @param n Node to be converted to a string. May be null. |
| * @return value of n as a string, or an empty string if n is null. |
| */ |
| public String toString(org.w3c.dom.Node n) |
| { |
| // %REVIEW% You can't get much uglier than this... |
| int nodeHandle = getDTMHandleFromNode(n); |
| DTM dtm = getDTM(nodeHandle); |
| XMLString strVal = dtm.getStringValue(nodeHandle); |
| return strVal.toString(); |
| } |
| |
| /** |
| * Get a variable based on it's qualified name. |
| * @param qname The qualified name of the variable. |
| * @return The evaluated value of the variable. |
| * @throws javax.xml.transform.TransformerException |
| */ |
| |
| public final XObject getVariableOrParam(org.apache.xml.utils.QName qname) |
| throws javax.xml.transform.TransformerException |
| { |
| return m_variableStacks.getVariableOrParam(XPathContext.this, qname); |
| } |
| |
| } |
| |
| /** |
| * Get a DTM to be used as a container for a global Result Tree |
| * Fragment. This will always be an instance of (derived from? equivalent to?) |
| * SAX2DTM, since each RTF is constructed by temporarily redirecting our SAX |
| * output to it. It may be a single DTM containing for multiple fragments, |
| * if the implementation supports that. |
| * |
| * Note: The distinction between this method and getRTFDTM() is that the latter |
| * allocates space from the dynamic variable stack (m_rtfdtm_stack), which may |
| * be pruned away again as the templates which defined those variables are exited. |
| * Global variables may be bound late (see XUnresolvedVariable), and never want to |
| * be discarded, hence we need to allocate them separately and don't actually need |
| * a stack to track them. |
| * |
| * @return a non-null DTM reference. |
| */ |
| public DTM getGlobalRTFDTM() |
| { |
| // We probably should _NOT_ be applying whitespace filtering at this stage! |
| // |
| // Some magic has been applied in DTMManagerDefault to recognize this set of options |
| // and generate an instance of DTM which can contain multiple documents |
| // (SAX2RTFDTM). Perhaps not the optimal way of achieving that result, but |
| // I didn't want to change the manager API at this time, or expose |
| // too many dependencies on its internals. (Ideally, I'd like to move |
| // isTreeIncomplete all the way up to DTM, so we wouldn't need to explicitly |
| // specify the subclass here.) |
| |
| // If it doesn't exist, or if the one already existing is in the middle of |
| // being constructed, we need to obtain a new DTM to write into. I'm not sure |
| // the latter will ever arise, but I'd rather be just a bit paranoid.. |
| if( m_global_rtfdtm==null || m_global_rtfdtm.isTreeIncomplete() ) |
| { |
| m_global_rtfdtm=(SAX2RTFDTM)m_dtmManager.getDTM(null,true,null,false,false); |
| } |
| return m_global_rtfdtm; |
| } |
| |
| |
| |
| |
| /** |
| * Get a DTM to be used as a container for a dynamic Result Tree |
| * Fragment. This will always be an instance of (derived from? equivalent to?) |
| * SAX2DTM, since each RTF is constructed by temporarily redirecting our SAX |
| * output to it. It may be a single DTM containing for multiple fragments, |
| * if the implementation supports that. |
| * |
| * @return a non-null DTM reference. |
| */ |
| public DTM getRTFDTM() |
| { |
| SAX2RTFDTM rtfdtm; |
| |
| // We probably should _NOT_ be applying whitespace filtering at this stage! |
| // |
| // Some magic has been applied in DTMManagerDefault to recognize this set of options |
| // and generate an instance of DTM which can contain multiple documents |
| // (SAX2RTFDTM). Perhaps not the optimal way of achieving that result, but |
| // I didn't want to change the manager API at this time, or expose |
| // too many dependencies on its internals. (Ideally, I'd like to move |
| // isTreeIncomplete all the way up to DTM, so we wouldn't need to explicitly |
| // specify the subclass here.) |
| |
| if(m_rtfdtm_stack==null) |
| { |
| m_rtfdtm_stack=new Vector(); |
| rtfdtm=(SAX2RTFDTM)m_dtmManager.getDTM(null,true,null,false,false); |
| m_rtfdtm_stack.addElement(rtfdtm); |
| ++m_which_rtfdtm; |
| } |
| else if(m_which_rtfdtm<0) |
| { |
| rtfdtm=(SAX2RTFDTM)m_rtfdtm_stack.elementAt(++m_which_rtfdtm); |
| } |
| else |
| { |
| rtfdtm=(SAX2RTFDTM)m_rtfdtm_stack.elementAt(m_which_rtfdtm); |
| |
| // It might already be under construction -- the classic example would be |
| // an xsl:variable which uses xsl:call-template as part of its value. To |
| // handle this recursion, we have to start a new RTF DTM, pushing the old |
| // one onto a stack so we can return to it. This is not as uncommon a case |
| // as we might wish, unfortunately, as some folks insist on coding XSLT |
| // as if it were a procedural language... |
| if(rtfdtm.isTreeIncomplete()) |
| { |
| if(++m_which_rtfdtm < m_rtfdtm_stack.size()) |
| rtfdtm=(SAX2RTFDTM)m_rtfdtm_stack.elementAt(m_which_rtfdtm); |
| else |
| { |
| rtfdtm=(SAX2RTFDTM)m_dtmManager.getDTM(null,true,null,false,false); |
| m_rtfdtm_stack.addElement(rtfdtm); |
| } |
| } |
| } |
| |
| return rtfdtm; |
| } |
| |
| /** Push the RTFDTM's context mark, to allows discarding RTFs added after this |
| * point. (If it doesn't exist we don't push, since we might still be able to |
| * get away with not creating it. That requires that excessive pops be harmless.) |
| * */ |
| public void pushRTFContext() |
| { |
| m_last_pushed_rtfdtm.push(m_which_rtfdtm); |
| if(null!=m_rtfdtm_stack) |
| ((SAX2RTFDTM)(getRTFDTM())).pushRewindMark(); |
| } |
| |
| /** Pop the RTFDTM's context mark. This discards any RTFs added after the last |
| * mark was set. |
| * |
| * If there is no RTF DTM, there's nothing to pop so this |
| * becomes a no-op. If pushes were issued before this was called, we count on |
| * the fact that popRewindMark is defined such that overpopping just resets |
| * to empty. |
| * |
| * Complicating factor: We need to handle the case of popping back to a previous |
| * RTF DTM, if one of the weird produce-an-RTF-to-build-an-RTF cases arose. |
| * Basically: If pop says this DTM is now empty, then return to the previous |
| * if one exists, in whatever state we left it in. UGLY, but hopefully the |
| * situation which forces us to consider this will arise exceedingly rarely. |
| * */ |
| public void popRTFContext() |
| { |
| int previous=m_last_pushed_rtfdtm.pop(); |
| if(null==m_rtfdtm_stack) |
| return; |
| |
| if(m_which_rtfdtm==previous) |
| { |
| if(previous>=0) // guard against none-active |
| { |
| boolean isEmpty=((SAX2RTFDTM)(m_rtfdtm_stack.elementAt(previous))).popRewindMark(); |
| } |
| } |
| else while(m_which_rtfdtm!=previous) |
| { |
| // Empty each DTM before popping, so it's ready for reuse |
| // _DON'T_ pop the previous, since it's still open (which is why we |
| // stacked up more of these) and did not receive a mark. |
| boolean isEmpty=((SAX2RTFDTM)(m_rtfdtm_stack.elementAt(m_which_rtfdtm))).popRewindMark(); |
| --m_which_rtfdtm; |
| } |
| } |
| |
| /** |
| * Gets DTMXRTreeFrag object if one has already been created. |
| * Creates new DTMXRTreeFrag object and adds to m_DTMXRTreeFrags HashMap, |
| * otherwise. |
| * @param dtmIdentity |
| * @return DTMXRTreeFrag |
| */ |
| public DTMXRTreeFrag getDTMXRTreeFrag(int dtmIdentity){ |
| if(m_DTMXRTreeFrags == null){ |
| m_DTMXRTreeFrags = new HashMap(); |
| } |
| |
| if(m_DTMXRTreeFrags.containsKey(new Integer(dtmIdentity))){ |
| return (DTMXRTreeFrag)m_DTMXRTreeFrags.get(new Integer(dtmIdentity)); |
| }else{ |
| final DTMXRTreeFrag frag = new DTMXRTreeFrag(dtmIdentity,this); |
| m_DTMXRTreeFrags.put(new Integer(dtmIdentity),frag); |
| return frag ; |
| } |
| } |
| |
| /** |
| * Cleans DTMXRTreeFrag objects by removing references |
| * to DTM and XPathContext objects. |
| */ |
| private final void releaseDTMXRTreeFrags(){ |
| if(m_DTMXRTreeFrags == null){ |
| return; |
| } |
| final Iterator iter = (m_DTMXRTreeFrags.values()).iterator(); |
| while(iter.hasNext()){ |
| DTMXRTreeFrag frag = (DTMXRTreeFrag)iter.next(); |
| frag.destruct(); |
| iter.remove(); |
| } |
| m_DTMXRTreeFrags = null; |
| } |
| } |