001/*-------------------------------------------------------------------------+
002|                                                                          |
003| Copyright 2005-2011 The ConQAT Project                                   |
004|                                                                          |
005| Licensed under the Apache License, Version 2.0 (the "License");          |
006| you may not use this file except in compliance with the License.         |
007| You may obtain a copy of the License at                                  |
008|                                                                          |
009|    http://www.apache.org/licenses/LICENSE-2.0                            |
010|                                                                          |
011| Unless required by applicable law or agreed to in writing, software      |
012| distributed under the License is distributed on an "AS IS" BASIS,        |
013| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
014| See the License for the specific language governing permissions and      |
015| limitations under the License.                                           |
016+-------------------------------------------------------------------------*/
017package org.conqat.lib.commons.xml;
018
019import java.util.Iterator;
020import java.util.List;
021
022import javax.xml.namespace.NamespaceContext;
023import javax.xml.namespace.QName;
024import javax.xml.xpath.XPath;
025import javax.xml.xpath.XPathConstants;
026import javax.xml.xpath.XPathExpressionException;
027import javax.xml.xpath.XPathFactory;
028
029import org.conqat.lib.commons.assertion.CCSMAssert;
030import org.conqat.lib.commons.collections.BidirectionalMap;
031import org.w3c.dom.Element;
032import org.w3c.dom.NodeList;
033
034/**
035 * Evaluator for XPath expression. This is preferable to using the normal
036 * {@link XPath} class as it has built-in support for namespace-handling and the
037 * evaluation-method does not define exceptions.
038 */
039public class XPathEvaluator {
040
041        /** XPath object used to evaluate Bugzilla result document.s */
042        private final XPath xPath = XPathFactory.newInstance().newXPath();
043
044        /** The namespace context to use. */
045        private final NSContext nsContext;
046
047        /** Create new evaluator. */
048        public XPathEvaluator() {
049                nsContext = new NSContext();
050                xPath.setNamespaceContext(nsContext);
051        }
052
053        /** Add a namespace. */
054        public void addNamespace(String prefix, String uri) {
055                nsContext.addNamespace(prefix, uri);
056        }
057
058        /**
059         * Evaluates an XPath expression on context element. This assumes that the
060         * XPath expression is valid and raises an {@link AssertionError} otherwise.
061         * 
062         * @param returnType
063         *            use {@link XPathConstants} to define return type.
064         */
065        public Object select(String expr, Element context, QName returnType) {
066                try {
067                        return selectUnsafe(expr, context, returnType);
068                } catch (XPathExpressionException e) {
069                        CCSMAssert.fail(e.getMessage());
070                        return null;
071                }
072        }
073
074        /**
075         * Evaluates an XPath expression on context element.
076         * 
077         * @param returnType
078         *            use {@link XPathConstants} to define return type.
079         * @throws XPathExpressionException
080         *             if the XPath expression is invalid.
081         */
082        public Object selectUnsafe(String expr, Element context, QName returnType) throws XPathExpressionException {
083                return xPath.evaluate(expr, context, returnType);
084        }
085
086        /**
087         * Evaluates an XPath expression on context element. This assumes that the
088         * XPath expression is valid and raises an {@link AssertionError} otherwise.
089         */
090        public List<Element> selectList(String expr, Element context) {
091                return XMLUtils.elementNodes(selectNodeList(expr, context));
092        }
093
094        /**
095         * Evaluates an XPath expression on context element. This assumes that the
096         * XPath expression is valid and raises an {@link AssertionError} otherwise.
097         */
098        public NodeList selectNodeList(String expr, Element context) {
099                return (NodeList) select(expr, context, XPathConstants.NODESET);
100        }
101
102        /**
103         * Evaluates an XPath expression on context element. This assumes that the
104         * XPath expression is valid and raises an {@link AssertionError} otherwise.
105         */
106        public Element selectElement(String expr, Element context) {
107                return (Element) select(expr, context, XPathConstants.NODE);
108        }
109
110        /**
111         * Evaluates an XPath expression on context element. This assumes that the
112         * XPath expression is valid and raises an {@link AssertionError} otherwise.
113         */
114        public String selectString(String expr, Element context) {
115                return (String) select(expr, context, XPathConstants.STRING);
116        }
117
118        /**
119         * Evaluates an XPath expression on context element. This assumes that the
120         * XPath expression is valid and raises an {@link AssertionError} otherwise.
121         * Due to the implementation of {@link XPath} this returns 0.0 if the
122         * element was not found.
123         */
124        public double selectDouble(String expr, Element context) {
125                return (Double) select(expr, context, XPathConstants.NUMBER);
126        }
127
128        /**
129         * Evaluates an XPath expression on context element. This assumes that the
130         * XPath expression is valid and raises an {@link AssertionError} otherwise.
131         * Due to the implementation of {@link XPath} this returns 0 if the element
132         * was not found.
133         */
134        public int selectInt(String expr, Element context) {
135                return ((Double) select(expr, context, XPathConstants.NUMBER)).intValue();
136        }
137
138        /**
139         * Evaluates an XPath expression on context element. This assumes that the
140         * XPath expression is valid and raises an {@link AssertionError} otherwise.
141         * Due to the implementation of {@link XPath} this returns
142         * <code>false</code> if the element was not found.
143         */
144        public boolean selectBoolean(String expr, Element context) {
145                return (Boolean) select(expr, context, XPathConstants.BOOLEAN);
146        }
147
148        /** Simple namespace context. */
149        private static class NSContext implements NamespaceContext {
150
151                /** Maps from prefix (first) to namespace URI (second). */
152                private final BidirectionalMap<String, String> map = new BidirectionalMap<String, String>();
153
154                /** Add new namespace to both maps. */
155                private void addNamespace(String prefix, String uri) {
156                        map.put(prefix, uri);
157                }
158
159                /** {@inheritDoc} */
160                @Override
161                public String getNamespaceURI(String prefix) {
162                        return map.getSecond(prefix);
163                }
164
165                /** {@inheritDoc} */
166                @Override
167                public String getPrefix(String namespaceURI) {
168                        return map.getFirst(namespaceURI);
169                }
170
171                /** {@inheritDoc} */
172                @Override
173                @SuppressWarnings("rawtypes")
174                public Iterator getPrefixes(String namespaceURI) {
175                        return map.getFirstSet().iterator();
176                }
177        }
178}