001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements. See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership. The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the  "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    /*
019     * $Id: XPathEvaluatorImpl.java 1225443 2011-12-29 05:44:18Z mrglavas $
020     */
021    
022    package org.apache.xpath.domapi;
023    
024    import javax.xml.transform.TransformerException;
025    
026    import org.apache.xml.utils.PrefixResolver;
027    import org.apache.xpath.XPath;
028    import org.apache.xpath.res.XPATHErrorResources;
029    import org.apache.xpath.res.XPATHMessages;
030    import org.w3c.dom.DOMException;
031    import org.w3c.dom.Document;
032    import org.w3c.dom.Node;
033    import org.w3c.dom.xpath.XPathEvaluator;
034    import org.w3c.dom.xpath.XPathException;
035    import org.w3c.dom.xpath.XPathExpression;
036    import org.w3c.dom.xpath.XPathNSResolver;
037    
038    /**
039     *
040     * The class provides an implementation of XPathEvaluator according 
041     * to the DOM L3 XPath Specification, Working Group Note 26 February 2004.
042     *
043     * <p>See also the <a href='http://www.w3.org/TR/2004/NOTE-DOM-Level-3-XPath-20040226'>Document Object Model (DOM) Level 3 XPath Specification</a>.</p>
044     * 
045     * </p>The evaluation of XPath expressions is provided by 
046     * <code>XPathEvaluator</code>, which will provide evaluation of XPath 1.0 
047     * expressions with no specialized extension functions or variables. It is 
048     * expected that the <code>XPathEvaluator</code> interface will be 
049     * implemented on the same object which implements the <code>Document</code> 
050     * interface in an implementation which supports the XPath DOM module. 
051     * <code>XPathEvaluator</code> implementations may be available from other 
052     * sources that may provide support for special extension functions or 
053     * variables which are not defined in this specification.</p>
054     * 
055     * @see org.w3c.dom.xpath.XPathEvaluator
056     * 
057     * @xsl.usage internal
058     */
059    public final class XPathEvaluatorImpl implements XPathEvaluator {
060    
061            /**
062             * This prefix resolver is created whenever null is passed to the 
063             * evaluate method.  Its purpose is to satisfy the DOM L3 XPath API
064             * requirement that if a null prefix resolver is used, an exception 
065             * should only be thrown when an attempt is made to resolve a prefix.
066             */
067            private static class DummyPrefixResolver implements PrefixResolver {
068    
069                    /**
070                     * Constructor for DummyPrefixResolver.
071                     */
072                    DummyPrefixResolver() {}
073                            
074                    /**
075                     * @exception DOMException
076             *   NAMESPACE_ERR: Always throws this exceptionn
077                     *
078                     * @see org.apache.xml.utils.PrefixResolver#getNamespaceForPrefix(String, Node)
079                     */
080                    public String getNamespaceForPrefix(String prefix, Node context) {
081                String fmsg = XPATHMessages.createXPATHMessage(XPATHErrorResources.ER_NULL_RESOLVER, null);       
082                throw new DOMException(DOMException.NAMESPACE_ERR, fmsg);   // Unable to resolve prefix with null prefix resolver.         
083                    }
084    
085                    /**
086                     * @exception DOMException
087             *   NAMESPACE_ERR: Always throws this exceptionn
088             * 
089                     * @see org.apache.xml.utils.PrefixResolver#getNamespaceForPrefix(String)
090                     */
091                    public String getNamespaceForPrefix(String prefix) {
092                            return getNamespaceForPrefix(prefix,null);
093                    }
094    
095                    /**
096                     * @see org.apache.xml.utils.PrefixResolver#handlesNullPrefixes()
097                     */
098                    public boolean handlesNullPrefixes() {
099                            return false;
100                    }
101    
102                    /**
103                     * @see org.apache.xml.utils.PrefixResolver#getBaseIdentifier()
104                     */
105                    public String getBaseIdentifier() {
106                            return null;
107                    }
108    
109            }
110    
111        /**
112         * The document to be searched to parallel the case where the XPathEvaluator
113         * is obtained by casting a Document.
114         */  
115        private final Document m_doc;
116        
117        /**
118         * Constructor for XPathEvaluatorImpl.
119         * 
120         * @param doc The document to be searched, to parallel the case where''
121         *            the XPathEvaluator is obtained by casting the document.
122         */
123        public XPathEvaluatorImpl(Document doc) {
124            m_doc = doc;
125        }
126        
127        /**
128         * Constructor in the case that the XPath expression can be evaluated
129         * without needing an XML document at all.
130         *
131         */
132        public XPathEvaluatorImpl() {
133                m_doc = null;
134        }
135    
136            /**
137         * Creates a parsed XPath expression with resolved namespaces. This is 
138         * useful when an expression will be reused in an application since it 
139         * makes it possible to compile the expression string into a more 
140         * efficient internal form and preresolve all namespace prefixes which 
141         * occur within the expression.
142         * 
143         * @param expression The XPath expression string to be parsed.
144         * @param resolver The <code>resolver</code> permits translation of 
145         *   prefixes within the XPath expression into appropriate namespace URIs
146         *   . If this is specified as <code>null</code>, any namespace prefix 
147         *   within the expression will result in <code>DOMException</code> 
148         *   being thrown with the code <code>NAMESPACE_ERR</code>.
149         * @return The compiled form of the XPath expression.
150         * @exception XPathException
151         *   INVALID_EXPRESSION_ERR: Raised if the expression is not legal 
152         *   according to the rules of the <code>XPathEvaluator</code>i
153         * @exception DOMException
154         *   NAMESPACE_ERR: Raised if the expression contains namespace prefixes 
155         *   which cannot be resolved by the specified 
156         *   <code>XPathNSResolver</code>.      
157         *  
158             * @see org.w3c.dom.xpath.XPathEvaluator#createExpression(String, XPathNSResolver)
159             */
160            public XPathExpression createExpression(
161                    String expression,
162                    XPathNSResolver resolver)
163                    throws XPathException, DOMException {
164                    
165                    try {
166                            
167                            // If the resolver is null, create a dummy prefix resolver
168                            XPath xpath =  new XPath(expression,null,
169                                 ((null == resolver) ? new DummyPrefixResolver() : ((PrefixResolver)resolver)), 
170                                  XPath.SELECT);
171                      
172                return new XPathExpressionImpl(xpath, m_doc);
173                            
174                    } catch (TransformerException e) {
175                            // Need to pass back exception code DOMException.NAMESPACE_ERR also.
176                            // Error found in DOM Level 3 XPath Test Suite.
177                            if(e instanceof XPathStylesheetDOM3Exception)
178                                    throw new DOMException(DOMException.NAMESPACE_ERR,e.getMessageAndLocation());
179                            else
180                                    throw new XPathException(XPathException.INVALID_EXPRESSION_ERR,e.getMessageAndLocation());
181                                    
182                    }
183            }
184    
185            /**
186         * Adapts any DOM node to resolve namespaces so that an XPath expression 
187         * can be easily evaluated relative to the context of the node where it 
188         * appeared within the document. This adapter works like the DOM Level 3 
189         * method <code>lookupNamespaceURI</code> on nodes in resolving the 
190         * namespaceURI from a given prefix using the current information available 
191         * in the node's hierarchy at the time lookupNamespaceURI is called, also 
192         * correctly resolving the implicit xml prefix.
193         *
194         * @param nodeResolver The node to be used as a context for namespace 
195         *   resolution.
196         * @return <code>XPathNSResolver</code> which resolves namespaces with 
197         *   respect to the definitions in scope for a specified node.
198         *  
199             * @see org.w3c.dom.xpath.XPathEvaluator#createNSResolver(Node)
200             */
201            public XPathNSResolver createNSResolver(Node nodeResolver) {
202            
203                    return new XPathNSResolverImpl((nodeResolver.getNodeType() == Node.DOCUMENT_NODE)
204                       ? ((Document) nodeResolver).getDocumentElement() : nodeResolver);
205            }
206    
207            /**
208         * Evaluates an XPath expression string and returns a result of the 
209         * specified type if possible.
210         * 
211         * @param expression The XPath expression string to be parsed and 
212         *   evaluated.
213         * @param contextNode The <code>context</code> is context node for the 
214         *   evaluation of this XPath expression. If the XPathEvaluator was 
215         *   obtained by casting the <code>Document</code> then this must be 
216         *   owned by the same document and must be a <code>Document</code>, 
217         *   <code>Element</code>, <code>Attribute</code>, <code>Text</code>, 
218         *   <code>CDATASection</code>, <code>Comment</code>, 
219         *   <code>ProcessingInstruction</code>, or <code>XPathNamespace</code> 
220         *   node. If the context node is a <code>Text</code> or a 
221         *   <code>CDATASection</code>, then the context is interpreted as the 
222         *   whole logical text node as seen by XPath, unless the node is empty 
223         *   in which case it may not serve as the XPath context.
224         * @param resolver The <code>resolver</code> permits translation of 
225         *   prefixes within the XPath expression into appropriate namespace URIs
226         *   . If this is specified as <code>null</code>, any namespace prefix 
227         *   within the expression will result in <code>DOMException</code> 
228         *   being thrown with the code <code>NAMESPACE_ERR</code>.
229         * @param type If a specific <code>type</code> is specified, then the 
230         *   result will be coerced to return the specified type relying on 
231         *   XPath type conversions and fail if the desired coercion is not 
232         *   possible. This must be one of the type codes of 
233         *   <code>XPathResult</code>.
234         * @param result The <code>result</code> specifies a specific result 
235         *   object which may be reused and returned by this method. If this is 
236         *   specified as <code>null</code>or the implementation does not reuse 
237         *   the specified result, a new result object will be constructed and 
238         *   returned.For XPath 1.0 results, this object will be of type 
239         *   <code>XPathResult</code>.
240         * @return The result of the evaluation of the XPath expression.For XPath 
241         *   1.0 results, this object will be of type <code>XPathResult</code>.
242         * @exception XPathException
243         *   INVALID_EXPRESSION_ERR: Raised if the expression is not legal 
244         *   according to the rules of the <code>XPathEvaluator</code>i
245         *   <br>TYPE_ERR: Raised if the result cannot be converted to return the 
246         *   specified type.
247         * @exception DOMException
248         *   NAMESPACE_ERR: Raised if the expression contains namespace prefixes 
249         *   which cannot be resolved by the specified 
250         *   <code>XPathNSResolver</code>.
251         *   <br>WRONG_DOCUMENT_ERR: The Node is from a document that is not 
252         *   supported by this XPathEvaluator.
253         *   <br>NOT_SUPPORTED_ERR: The Node is not a type permitted as an XPath 
254         *   context node.
255             * 
256             * @see org.w3c.dom.xpath.XPathEvaluator#evaluate(String, Node, XPathNSResolver, short, XPathResult)
257             */
258            public Object evaluate(
259                    String expression,
260                    Node contextNode,
261                    XPathNSResolver resolver,
262                    short type,
263                    Object result)
264                    throws XPathException, DOMException {
265                            
266                    XPathExpression xpathExpression = createExpression(expression, resolver);
267                    
268                    return  xpathExpression.evaluate(contextNode, type, result);
269            }
270    
271    }