001: /*
002: ******************************************************************
003: Copyright (c) 2001-2007, Jeff Martin, Tim Bacon
004: All rights reserved.
005:
006: Redistribution and use in source and binary forms, with or without
007: modification, are permitted provided that the following conditions
008: are met:
009:
010: * Redistributions of source code must retain the above copyright
011: notice, this list of conditions and the following disclaimer.
012: * Redistributions in binary form must reproduce the above
013: copyright notice, this list of conditions and the following
014: disclaimer in the documentation and/or other materials provided
015: with the distribution.
016: * Neither the name of the xmlunit.sourceforge.net nor the names
017: of its contributors may be used to endorse or promote products
018: derived from this software without specific prior written
019: permission.
020:
021: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
022: "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
023: LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
024: FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
025: COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
026: INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
027: BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
028: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
029: CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
030: LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
031: ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
032: POSSIBILITY OF SUCH DAMAGE.
033:
034: ******************************************************************
035: */
036:
037: package org.custommonkey.xmlunit;
038:
039: import org.custommonkey.xmlunit.exceptions.ConfigurationException;
040: import org.custommonkey.xmlunit.exceptions.XpathException;
041:
042: import java.io.StringReader;
043: import java.io.StringWriter;
044: import java.util.Iterator;
045: import javax.xml.transform.Result;
046: import javax.xml.transform.Transformer;
047: import javax.xml.transform.TransformerException;
048: import javax.xml.transform.dom.DOMResult;
049: import javax.xml.transform.dom.DOMSource;
050: import javax.xml.transform.stream.StreamResult;
051: import javax.xml.transform.stream.StreamSource;
052: import org.w3c.dom.Document;
053: import org.w3c.dom.Node;
054: import org.w3c.dom.NodeList;
055:
056: /**
057: * Simple class for accessing the Nodes matched by an Xpath expression, or
058: * evaluating the String value of an Xpath expression.
059: * Uses a <code>copy-of</code> or <code>value-of</code> XSL template (as
060: * appropriate) to execute the Xpath.
061: * This is not an efficient method for accessing XPaths but it is portable
062: * across underlying transform implementations. (Yes I know Jaxen is too, but
063: * this approach seemed to be the simplest thing that could possibly work...)
064: * <br />Examples and more at <a href="http://xmlunit.sourceforge.net"/>xmlunit.sourceforge.net</a>
065: */
066: public class SimpleXpathEngine implements XpathEngine, XSLTConstants {
067:
068: private NamespaceContext ctx = SimpleNamespaceContext.EMPTY_CONTEXT;
069:
070: /**
071: * What every XSL transform needs
072: * @return
073: */
074: private StringBuffer getXSLTBase() {
075: StringBuffer result = new StringBuffer(XML_DECLARATION)
076: .append(XMLUnit.getXSLTStart());
077: String tmp = result.toString();
078: int close = tmp.lastIndexOf('>');
079: if (close == -1) {
080: close = tmp.length();
081: }
082: result.insert(close, getNamespaceDeclarations());
083: return result;
084: }
085:
086: /**
087: * @param select an xpath syntax <code>select</code> expression
088: * @return the <code>copy-of</code> transformation
089: */
090: private String getCopyTransformation(String select) {
091: return getXSLTBase()
092: .append("<xsl:preserve-space elements=\"*\"/>")
093: .append(
094: "<xsl:output method=\"xml\" version=\"1.0\" encoding=\"UTF-8\"/>")
095: .append("<xsl:template match=\"/\">").append(
096: "<xpathResult>").append(
097: "<xsl:apply-templates select=\"")
098: .append(select).append("\" mode=\"result\"/>").append(
099: "</xpathResult>").append("</xsl:template>")
100: .append("<xsl:template match=\"*\" mode=\"result\">")
101: .append(" <xsl:copy-of select=\".\"/>").append(
102: "</xsl:template>").append("</xsl:stylesheet>")
103: .toString();
104: }
105:
106: /**
107: * @param select an xpath syntax <code>select</code> expression
108: * @return the <code>value-of</code> transformation
109: */
110: private String getValueTransformation(String select) {
111: return getXSLTBase().append("<xsl:output method=\"text\"/>")
112: .append("<xsl:template match=\"/\">").append(
113: " <xsl:value-of select=\"").append(select)
114: .append("\"/>").append("</xsl:template>").append(
115: "</xsl:stylesheet>").toString();
116: }
117:
118: /**
119: * Perform the actual transformation work required
120: * @param xslt
121: * @param document
122: * @param result
123: * @throws TransformerException
124: * @throws ConfigurationException
125: */
126: private void performTransform(String xslt, Document document,
127: Result result) throws TransformerException,
128: ConfigurationException {
129: try {
130: StreamSource source = new StreamSource(new StringReader(
131: xslt));
132: Transformer transformer = XMLUnit.getTransformerFactory()
133: .newTransformer(source);
134: transformer.transform(new DOMSource(document), result);
135: } catch (javax.xml.transform.TransformerConfigurationException ex) {
136: throw new ConfigurationException(ex);
137: }
138: }
139:
140: /**
141: * Testable method to execute the copy-of transform and return the root
142: * node of the resulting Document.
143: * @param select
144: * @param document
145: * @throws ConfigurationException
146: * @throws TransformerException
147: * @return the root node of the Document created by the copy-of transform.
148: */
149: protected Node getXPathResultNode(String select, Document document)
150: throws ConfigurationException, TransformerException {
151: return getXPathResultAsDocument(select, document)
152: .getDocumentElement();
153: }
154:
155: /**
156: * Execute the copy-of transform and return the resulting Document.
157: * Used for XMLTestCase comparison
158: * @param select
159: * @param document
160: * @throws ConfigurationException
161: * @throws TransformerException
162: * @return the Document created by the copy-of transform.
163: */
164: protected Document getXPathResultAsDocument(String select,
165: Document document) throws ConfigurationException,
166: TransformerException {
167: DOMResult result = new DOMResult();
168: performTransform(getCopyTransformation(select), document,
169: result);
170: return (Document) result.getNode();
171: }
172:
173: /**
174: * Execute the specified xpath syntax <code>select</code> expression
175: * on the specified document and return the list of nodes (could have
176: * length zero) that match
177: * @param select
178: * @param document
179: * @return list of matching nodes
180: */
181: public NodeList getMatchingNodes(String select, Document document)
182: throws ConfigurationException, XpathException {
183: try {
184: return getXPathResultNode(select, document).getChildNodes();
185: } catch (TransformerException ex) {
186: throw new XpathException("Failed to apply stylesheet", ex);
187: }
188: }
189:
190: /**
191: * Evaluate the result of executing the specified xpath syntax
192: * <code>select</code> expression on the specified document
193: * @param select
194: * @param document
195: * @return evaluated result
196: */
197: public String evaluate(String select, Document document)
198: throws ConfigurationException, XpathException {
199: try {
200: StringWriter writer = new StringWriter();
201: StreamResult result = new StreamResult(writer);
202: performTransform(getValueTransformation(select), document,
203: result);
204: return writer.toString();
205: } catch (TransformerException ex) {
206: throw new XpathException("Failed to apply stylesheet", ex);
207: }
208: }
209:
210: public void setNamespaceContext(NamespaceContext ctx) {
211: this .ctx = ctx;
212: }
213:
214: /**
215: * returns namespace declarations for all namespaces known to the
216: * current context.
217: */
218: private String getNamespaceDeclarations() {
219: StringBuffer nsDecls = new StringBuffer();
220: String quoteStyle = "'";
221: for (Iterator keys = ctx.getPrefixes(); keys.hasNext();) {
222: String prefix = (String) keys.next();
223: String uri = ctx.getNamespaceURI(prefix);
224: if (uri == null) {
225: continue;
226: }
227: // this shouldn't have happened, but better safe than sorry
228: if (prefix == null) {
229: prefix = "";
230: }
231:
232: if (uri.indexOf('\'') != -1) {
233: quoteStyle = "\"";
234: }
235: nsDecls.append(' ').append(XMLNS_PREFIX);
236: if (prefix.length() > 0) {
237: nsDecls.append(':');
238: }
239: nsDecls.append(prefix).append('=').append(quoteStyle)
240: .append(uri).append(quoteStyle).append(' ');
241: }
242: return nsDecls.toString();
243: }
244: }
|