diff options
3 files changed, 190 insertions, 43 deletions
diff --git a/xml/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java b/xml/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java index 82cd25e..275bbf3 100644 --- a/xml/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java +++ b/xml/src/main/java/org/apache/harmony/xml/dom/InnerNodeImpl.java @@ -28,14 +28,16 @@ import java.util.List; * Provides a straightforward implementation of the corresponding W3C DOM * interface. The class is used internally only, thus only notable members that * are not in the original interface are documented (the W3C docs are quite - * extensive). Hope that's ok. - * <p> - * Some of the fields may have package visibility, so other classes belonging to - * the DOM implementation can easily access them while maintaining the DOM tree - * structure. - * <p> - * This class represents a Node that has a parent Node as well as (potentially) - * a number of children. + * extensive). + * + * <p>Some of the fields may have package visibility, so other classes belonging + * to the DOM implementation can easily access them while maintaining the DOM + * tree structure. + * + * <p>This class represents a Node that has a parent Node as well as + * (potentially) a number of children. + * + * <p>Some code was adapted from Apache Xerces. */ public abstract class InnerNodeImpl extends LeafNodeImpl { diff --git a/xml/src/main/java/org/apache/xalan/processor/TransformerFactoryImpl.java b/xml/src/main/java/org/apache/xalan/processor/TransformerFactoryImpl.java index 78d0cc5..618b412 100644 --- a/xml/src/main/java/org/apache/xalan/processor/TransformerFactoryImpl.java +++ b/xml/src/main/java/org/apache/xalan/processor/TransformerFactoryImpl.java @@ -792,7 +792,7 @@ public class TransformerFactoryImpl extends SAXTransformerFactory try { m_errorListener.fatalError( ex ); - return null; + return null; // TODO: but the API promises to never return null... } catch( TransformerConfigurationException ex1 ) { diff --git a/xml/src/test/java/org/apache/harmony/xml/XsltXPathConformanceTestSuite.java b/xml/src/test/java/org/apache/harmony/xml/XsltXPathConformanceTestSuite.java index 443e415..7dbfaea 100644 --- a/xml/src/test/java/org/apache/harmony/xml/XsltXPathConformanceTestSuite.java +++ b/xml/src/test/java/org/apache/harmony/xml/XsltXPathConformanceTestSuite.java @@ -23,15 +23,16 @@ import junit.framework.TestCase; import junit.framework.TestSuite; import junit.textui.TestRunner; import org.w3c.dom.Attr; -import org.w3c.dom.Comment; import org.w3c.dom.Document; import org.w3c.dom.Element; +import org.w3c.dom.EntityReference; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.ProcessingInstruction; -import org.w3c.dom.Text; +import org.xml.sax.InputSource; import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import org.xmlpull.v1.XmlSerializer; @@ -39,17 +40,25 @@ import org.xmlpull.v1.XmlSerializer; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.ErrorListener; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; +import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; @@ -61,13 +70,23 @@ import java.util.List; * XSLT conformance test suite</a>, adapted for use by JUnit. To run these tests * on a device: * <ul> - * <li>Obtain the <a href="http://www.oasis-open.org/committees/download.php/12171/XSLT-testsuite-04.ZIP">test - * suite zip file from the OASIS project site.</li> - * <li>Unzip. - * <li>Copy the files to a device: <code>adb shell mkdir /data/oasis ; - * adb push ./XSLT-Conformance-TC /data/oasis</code>. - * <li>Invoke this class' main method, passing the on-device path to the test - * suite's <code>catalog.xml</code> file as an argument. + * <li>Obtain the <a href="http://www.oasis-open.org/committees/download.php/12171/XSLT-testsuite-04.ZIP">test + * suite zip file from the OASIS project site.</li> + * <li>Unzip. + * <li>Copy the files to a device: <code>adb shell mkdir /data/oasis ; + * adb push ./XSLT-Conformance-TC /data/oasis</code>. + * <li>Invoke this class' main method, passing the on-device path to the test + * suite's <code>catalog.xml</code> file as an argument. + * </ul> + * + * <p>Unfortunately, some of the tests in the OASIS suite will fail when + * executed outside of their original development environment: + * <ul> + * <li>The tests assume case insensitive filesystems. Some will fail with + * "Couldn't open file" errors due to a mismatch in file name casing. + * <li>The tests assume certain network hosts will exist and serve + * stylesheet files. In particular, "http://webxtest/" isn't generally + * available. * </ul> */ public class XsltXPathConformanceTestSuite { @@ -164,7 +183,7 @@ public class XsltXPathConformanceTestSuite { /** * Returns a JUnit test for the test described by the given element. */ - private Test create(File base, Element testCaseElement) { + private TestCase create(File base, Element testCaseElement) { /* * Extract the XSLT test from a DOM entity with the following structure: @@ -291,7 +310,6 @@ public class XsltXPathConformanceTestSuite { * the result to an expected output file. */ public class XsltTest extends TestCase { - // TODO: include these in toString private final String category; private final String id; private final String purpose; @@ -304,7 +322,10 @@ public class XsltXPathConformanceTestSuite { /** either "standard" or "execution-error" */ private final String operation; - /** the syntax to compare the output file using, such as "XML" or "HTML" */ + /** + * The syntax to compare the output file using, such as "XML", "HTML", + * "manual", or null for expected execution errors. + */ private final String compareAs; XsltTest(String category, String id, String purpose, String spec, @@ -335,37 +356,50 @@ public class XsltXPathConformanceTestSuite { System.out.println("Spec: " + spec); } - Source xslt = new StreamSource(principalStylesheet); - Source in = new StreamSource(principalData); + Result result; + if ("XML".equals(compareAs)) { + DOMResult domResult = new DOMResult(); + domResult.setNode(documentBuilder.newDocument().createElementNS("", "result")); + result = domResult; + } else { + result = new StreamResult(new StringWriter()); + } + + ErrorRecorder errorRecorder = new ErrorRecorder(); + transformerFactory.setErrorListener(errorRecorder); Transformer transformer; try { + Source xslt = new StreamSource(principalStylesheet); transformer = transformerFactory.newTransformer(xslt); - assertEquals("Transformer creation completed normally.", - operation, "standard"); + if (errorRecorder.error == null) { + transformer.setErrorListener(errorRecorder); + transformer.transform(new StreamSource(principalData), result); + } } catch (TransformerConfigurationException e) { - if (operation.equals("execution-error")) { - return; // expected, such as in XSLT-Result-Tree.Attributes__78369 + errorRecorder.fatalError(e); + } + + if (operation.equals("standard")) { + if (errorRecorder.error != null) { + throw errorRecorder.error; + } + } else if (operation.equals("execution-error")) { + if (errorRecorder.error != null) { + return; } - AssertionFailedError failure = new AssertionFailedError(); - failure.initCause(e); - throw failure; + fail("Expected " + operation + ", but transform completed normally." + + " (Warning=" + errorRecorder.warning + ")"); + } else { + throw new UnsupportedOperationException("Unexpected operation: " + operation); } - Result result; - if (compareAs.equals("XML")) { - result = new DOMResult(); + if ("XML".equals(compareAs)) { + assertNodesAreEquivalent(principal, ((DOMResult) result).getNode()); } else { // TODO: implement support for comparing HTML etc. throw new UnsupportedOperationException("Cannot compare as " + compareAs); } - - transformer.transform(in, result); - - if (compareAs.equals("XML")) { - DOMResult domResult = (DOMResult) result; - assertNodesAreEquivalent(principal, domResult.getNode()); - } } @Override public String getName() { @@ -376,19 +410,65 @@ public class XsltXPathConformanceTestSuite { /** * Ensures both XML documents represent the same semantic data. Non-semantic * data such as namespace prefixes, comments, and whitespace is ignored. + * + * @param actual an XML document whose root is a {@code <result>} element. + * @param expected a file containing an XML document fragment. */ private void assertNodesAreEquivalent(File expected, Node actual) throws ParserConfigurationException, IOException, SAXException, XmlPullParserException { - Document expectedDocument = documentBuilder.parse(new FileInputStream(expected)); - String expectedString = nodeToNormalizedString(expectedDocument); + Node expectedNode = fileToResultNode(expected); + String expectedString = nodeToNormalizedString(expectedNode); String actualString = nodeToNormalizedString(actual); Assert.assertEquals("Expected XML to match file " + expected, expectedString, actualString); } + /** + * Returns the given file's XML fragment as a single node, wrapped in + * {@code <result>} tags. This takes care of normalizing the following + * conditions: + * + * <ul> + * <li>Files containing XML document fragments with multiple elements: + * {@code <SPAN style="color=blue">Smurfs!</SPAN><br />} + * + * <li>Files containing XML document fragments with no elements: + * {@code Smurfs!} + * + * <li>Files containing proper XML documents with a single element and an + * XML declaration: + * {@code <?xml version="1.0"?><doc />} + * + * <li>Files prefixed with a byte order mark header, such as 0xEFBBBF. + * </ul> + */ + private Node fileToResultNode(File file) throws IOException, SAXException { + String rawContents = fileToString(file); + String fragment = rawContents; + + // If the file had an XML declaration, strip that. Otherwise wrapping + // it in <result> tags would result in a malformed XML document. + if (fragment.startsWith("<?xml")) { + int declarationEnd = fragment.indexOf("?>"); + fragment = fragment.substring(declarationEnd + 2); + } + + // Parse it as document fragment wrapped in <result> tags. + try { + fragment = "<result>" + fragment + "</result>"; + return documentBuilder.parse(new InputSource(new StringReader(fragment))) + .getDocumentElement(); + } catch (SAXParseException e) { + Error error = new AssertionFailedError( + "Failed to parse XML: " + file + "\n" + rawContents); + error.initCause(e); + throw error; + } + } + private String nodeToNormalizedString(Node node) throws XmlPullParserException, IOException { StringWriter writer = new StringWriter(); @@ -411,7 +491,8 @@ public class XsltXPathConformanceTestSuite { emitChildren(serializer, element); serializer.endTag(element.getNamespaceURI(), element.getLocalName()); - } else if (node.getNodeType() == Node.TEXT_NODE) { + } else if (node.getNodeType() == Node.TEXT_NODE + || node.getNodeType() == Node.CDATA_SECTION_NODE) { // TODO: is it okay to trim whitespace in general? This may cause // false positives for elements like HTML's <pre> tag String trimmed = node.getTextContent().trim(); @@ -434,6 +515,10 @@ public class XsltXPathConformanceTestSuite { } else if (node.getNodeType() == Node.COMMENT_NODE) { // ignore! + } else if (node.getNodeType() == Node.ENTITY_REFERENCE_NODE) { + EntityReference entityReference = (EntityReference) node; + serializer.entityRef(entityReference.getNodeName()); + } else { throw new UnsupportedOperationException( "Cannot emit " + node + " of type " + node.getNodeType()); @@ -488,4 +573,64 @@ public class XsltXPathConformanceTestSuite { } return result; } + + /** + * Reads the given file into a string. If the file contains a byte order + * mark, the corresponding character set will be used. Otherwise the system + * default charset will be used. + */ + private String fileToString(File file) throws IOException { + InputStream in = new BufferedInputStream(new FileInputStream(file), 1024); + + // Read the byte order mark to determine the charset. + // TODO: use a built-in API for this... + Reader reader; + in.mark(3); + int byte1 = in.read(); + int byte2 = in.read(); + if (byte1 == 0xFF && byte2 == 0xFE) { + reader = new InputStreamReader(in, "UTF-16LE"); + } else if (byte1 == 0xFF && byte2 == 0xFF) { + reader = new InputStreamReader(in, "UTF-16BE"); + } else { + int byte3 = in.read(); + if (byte1 == 0xEF && byte2 == 0xBB && byte3 == 0xBF) { + reader = new InputStreamReader(in, "UTF-8"); + } else { + in.reset(); + reader = new InputStreamReader(in); + } + } + + StringWriter out = new StringWriter(); + char[] buffer = new char[1024]; + int count; + while ((count = reader.read(buffer)) != -1) { + out.write(buffer, 0, count); + } + return out.toString(); + } + + static class ErrorRecorder implements ErrorListener { + Exception warning; + Exception error; + + public void warning(TransformerException exception) { + if (this.warning == null) { + this.warning = exception; + } + } + + public void error(TransformerException exception) { + if (this.error == null) { + this.error = exception; + } + } + + public void fatalError(TransformerException exception) { + if (this.error == null) { + this.error = exception; + } + } + } } |