Commits

Matthias Nüßler committed 285e0fc

Improve XSLT test utilities.

  • Participants
  • Parent commits a5b0e75

Comments (0)

Files changed (9)

src/test/java/org/nuessler/maven/plugin/cakupan/testutil/CollectingErrorHandler.java

+package org.nuessler.maven.plugin.cakupan.testutil;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+import com.google.common.collect.Lists;
+
+/**
+ * {@link ErrorHandler} implementation that collects all parsing errors and
+ * warning in lists for later checking.
+ *
+ * @author Matthias Nuessler
+ */
+public class CollectingErrorHandler implements ErrorHandler {
+    private final List<SAXParseException> errors = Lists.newArrayList();
+    private final List<SAXParseException> fatalErrors = Lists.newArrayList();
+    private final List<SAXParseException> warnings = Lists.newArrayList();
+
+    @Override
+    public void error(SAXParseException e) throws SAXException {
+        errors.add(e);
+    }
+
+    @Override
+    public void fatalError(SAXParseException e) throws SAXException {
+        fatalErrors.add(e);
+    }
+
+    @Override
+    public void warning(SAXParseException e) throws SAXException {
+        warnings.add(e);
+    }
+
+    /**
+     * @return <code>true</code> if the error handler collected any errors
+     *         (fatal or normal).
+     */
+    public boolean hasErrors() {
+        return !(errors.isEmpty() && fatalErrors.isEmpty());
+    }
+
+    /**
+     * @return an unmodifiable list of collected errors
+     */
+    public List<SAXParseException> getErrors() {
+        return Collections.unmodifiableList(errors);
+    }
+
+    /**
+     * @return an unmodifiable list of collected fatal errors
+     */
+    public List<SAXParseException> getFatalErrors() {
+        return Collections.unmodifiableList(fatalErrors);
+    }
+
+    /**
+     * @return an unmodifiable list of collected warnings
+     */
+    public List<SAXParseException> getWarnings() {
+        return Collections.unmodifiableList(warnings);
+    }
+
+}

src/test/java/org/nuessler/maven/plugin/cakupan/testutil/DomUtil.java

 import java.io.StringWriter;
 
 import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.transform.Transformer;
 import javax.xml.transform.TransformerException;
 import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMSource;
 import org.xml.sax.InputSource;
 
 /**
- * Provides utility methods to convert a DOM document into a string and vice versa.
+ * Provides utility methods to convert a DOM document into a string and vice
+ * versa.
  *
  * @author Matthias Nuessler
  */
         // prevent instantiation
     }
 
+    /**
+     * Converts an XML string into a DOM {@link Document}.
+     *
+     * @param xml
+     *            the XML string to convert
+     * @return the equivalent XML DOM document
+     * @throws Exception
+     *             if the given string does not contain valid XML or conversion
+     *             fails for other reasons
+     */
     public static Document stringToDocument(String xml) throws Exception {
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
         factory.setNamespaceAware(true);
         return factory.newDocumentBuilder().parse(input);
     }
 
+    /**
+     * Converts a DOM {@link Document} into a string.
+     *
+     * @param doc
+     *            the DOM document to convert
+     * @return the XML document as a string
+     * @throws TransformerException
+     *             if conversion fails
+     */
     public static String documentToString(Document doc) throws TransformerException {
         DOMSource source = new DOMSource(doc);
         StringWriter writer = new StringWriter();
         StreamResult result = new StreamResult(writer);
         TransformerFactory factory = TransformerFactory.newInstance();
-        Transformer transformer = factory.newTransformer();
-        transformer.transform(source, result);
+        factory.newTransformer().transform(source, result);
         return writer.toString();
     }
 

src/test/java/org/nuessler/maven/plugin/cakupan/testutil/LocalResourceEntityResolver.java

+/**
+ * Copyright 2011 Matthias Nuessler <m.nuessler@web.de>
+ *
+ * Licensed 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.
+ */
+package org.nuessler.maven.plugin.cakupan.testutil;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.logging.Logger;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import com.google.common.collect.Maps;
+
+/**
+ * XML {@link EntityResolver} which resolves system IDs (i.e. schema URLs) from
+ * local resources. Can be used to avoid (repeated) downloads of schema files
+ * when validating against a schema. Requires a mapping from the system ID to a
+ * local resource name which can then be used to read the resource using
+ * {@link Class#getResourceAsStream(String)}.
+ *
+ * @author Matthias Nuessler
+ */
+public class LocalResourceEntityResolver implements EntityResolver {
+    private static final Logger LOG = Logger.getLogger(LocalResourceEntityResolver.class.getName());
+    private final Map<String, String> systemIdToResource = Maps.newLinkedHashMap();
+
+    /**
+     * @param systemIdToResource
+     *            a map to map system IDs (key) to local resource names (value).
+     */
+    public LocalResourceEntityResolver(Map<String, String> systemIdToResource) {
+        this.systemIdToResource.putAll(systemIdToResource);
+    }
+
+    @Override
+    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+        String resourceName = systemIdToResource.get(systemId);
+        if (resourceName == null) {
+            String errorMsg = String.format("No resource name found for system ID '%s'.", systemId);
+            LOG.warning(errorMsg);
+            throw new IOException(errorMsg);
+        }
+
+        InputStream in = getClass().getResourceAsStream(resourceName);
+        if (in == null) {
+            String errorMsg = String.format("Resource '%s' for system ID '%s' not found.", resourceName, systemId);
+            LOG.warning(errorMsg);
+            throw new IOException(errorMsg);
+        }
+
+        InputSource source = new InputSource(in);
+        source.setSystemId(systemId);
+        source.setPublicId(publicId);
+        return source;
+    }
+
+}

src/test/java/org/nuessler/maven/plugin/cakupan/testutil/LoggingEntityResolver.java

+package org.nuessler.maven.plugin.cakupan.testutil;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * {@link EntityResolver} which logs <code>publicId</code> and
+ * <code>systemId</code> to resolve and then delegates to the given
+ * {@link EntityResolver} delegate or to the {@link DefaultHandler} if none
+ * given. May be useful for debugging.
+ *
+ * @author Matthias Nuessler
+ */
+public class LoggingEntityResolver implements EntityResolver {
+    private static final Logger LOG = Logger.getLogger(LoggingEntityResolver.class.getName());
+    private final EntityResolver delegate;
+
+    public LoggingEntityResolver() {
+        this(new DefaultHandler());
+    }
+
+    public LoggingEntityResolver(EntityResolver delegate) {
+        this.delegate = delegate;
+    }
+
+    @Override
+    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+        LOG.info(String.format("Resolving publicId='%s', systemId='%s'", publicId, systemId));
+        return delegate.resolveEntity(publicId, systemId);
+    }
+}

src/test/java/org/nuessler/maven/plugin/cakupan/testutil/SimpleHttpEntityResolver.java

+package org.nuessler.maven.plugin.cakupan.testutil;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ * Simple {@link EntityResolver} which retrieves the entity via HTTP if the
+ * given system ID is a valid {@link URL}. Returns <code>null</code> otherwise.
+ *
+ * @author Matthias Nuessler
+ */
+public class SimpleHttpEntityResolver implements EntityResolver {
+
+    @Override
+    public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException {
+        URLConnection con = new URL(systemId).openConnection();
+        InputSource source = new InputSource(con.getInputStream());
+        source.setSystemId(systemId);
+        source.setPublicId(publicId);
+        return source;
+    }
+
+}

src/test/java/org/nuessler/maven/plugin/cakupan/testutil/SimpleNamespaceContext.java

 
 import com.google.common.collect.HashBiMap;
 
+/**
+ * Simple Map-based {@link NamespaceContext} implementation.
+ *
+ * @author Matthias Nuessler
+ * @see org.hamcrest.xml.HasXPath#hasXPath(String, NamespaceContext)
+ * @see org.hamcrest.xml.HasXPath#hasXPath(String, NamespaceContext,
+ *      org.hamcrest.Matcher)
+ */
 public class SimpleNamespaceContext implements NamespaceContext {
     private final HashBiMap<String, String> namespaces = HashBiMap.create();
 
+    /**
+     * Creates an empty context.
+     */
     public SimpleNamespaceContext() {
     }
 
+    /**
+     * Creates a context instance containing a single prefix and namespaxce URI.
+     *
+     * @param prefix
+     *            the namespace prefix
+     * @param namespaceURI
+     *            the URI mapped to the given prefix.
+     */
     public SimpleNamespaceContext(String prefix, String namespaceURI) {
         namespaces.put(prefix, namespaceURI);
     }
 
+    /**
+     * Creates a context instance using the provided map. The map must contain
+     * prefixes as keys and URIs as values.
+     *
+     * @param prefixNamespaceMap
+     *            the map with prefix / namespace URI pairs the context should
+     *            contain initially.
+     */
     public SimpleNamespaceContext(Map<String, String> prefixNamespaceMap) {
         namespaces.putAll(prefixNamespaceMap);
     }
 
+    /**
+     * Adds the given prefix and namespace mapping to the context.
+     *
+     * @param prefix
+     *            the prefix
+     * @param namespaceURI
+     *            the namespace URI for the given prefix.
+     * @return the current instance to allow method chaining.
+     */
     public SimpleNamespaceContext put(String prefix, String namespaceURI) {
         namespaces.put(prefix, namespaceURI);
         return this;

src/test/java/org/nuessler/maven/plugin/cakupan/testutil/TransformationTest.java

 
 import java.io.File;
 import java.net.URL;
+import java.util.Map;
 
+import javax.annotation.Nullable;
 import javax.xml.namespace.NamespaceContext;
 
 import org.w3c.dom.Document;
+import org.xml.sax.EntityResolver;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Maps;
 
 public class TransformationTest extends XslTransformationTestCase {
     private static final String TRUE = Boolean.TRUE.toString();
+    private static final String SCHEMA_PATH = "/schemas/xhtml/";
+    private static Map<String, String> systemIdToResource = createSystemIdToResourceMap();
 
     @Override
     protected File getTransformationFile() {
 
     @Override
     protected File getTargetSchemaFile() {
-        return resourceNameToFile("/xhtml-basic.xsd");
+        return resourceNameToFile(SCHEMA_PATH + "xhtml-basic.xsd");
+    }
+
+    @Override
+    protected EntityResolver getEntityResolver() {
+        return new LocalResourceEntityResolver(systemIdToResource);
     }
 
     private File resourceNameToFile(String resource) {
         assertThat(doc, hasXPath("//x:title", ctx, equalTo("Reading List")));
 
         assertThat(doc, hasXPath("//x:table/x:th/text()='Title'", ctx, equalTo(TRUE)));
+        assertThat(doc, hasXPath("//x:table/x:th", ctx, equalTo("Title")));
         assertThat(doc, hasXPath("//x:table/x:th/text()='Author'", ctx, equalTo(TRUE)));
         assertThat(doc, hasXPath("//x:table/x:th/text()='Pages'", ctx, equalTo(TRUE)));
         assertThat(doc, hasXPath("//x:table/x:th/text()='Publisher'", ctx, equalTo(TRUE)));
         assertThat(doc, hasXPath("//x:tr[@id='5']/x:td[6]", ctx, equalTo("0321743121")));
     }
 
+    /*
+     * Creates a map which maps all system IDs required to validate an XHTML
+     * document to local resources. So it can be avoided to download the files
+     * for every validation using a custom EntityResolver.
+     */
+    private static Map<String, String> createSystemIdToResourceMap() {
+        Map<String, String> m = Maps.newLinkedHashMap();
+
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-datatypes-1.xsd", "xhtml-datatypes-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-framework-1.xsd", "xhtml-framework-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-attribs-1.xsd", "xhtml-attribs-1.xsd");
+        m.put("http://www.w3.org/2001/xml.xsd", "xml.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-text-1.xsd", "xhtml-text-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-blkphras-1.xsd", "xhtml-blkphras-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-blkstruct-1.xsd", "xhtml-blkstruct-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-inlphras-1.xsd", "xhtml-inlphras-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-inlstruct-1.xsd", "xhtml-inlstruct-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-hypertext-1.xsd", "xhtml-hypertext-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-list-1.xsd", "xhtml-list-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-struct-1.xsd", "xhtml-struct-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-pres-1.xsd", "xhtml-pres-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-blkpres-1.xsd", "xhtml-blkpres-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-inlpres-1.xsd", "xhtml-inlpres-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-link-1.xsd", "xhtml-link-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-meta-1.xsd", "xhtml-meta-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-base-1.xsd", "xhtml-base-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-script-1.xsd", "xhtml-script-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-style-1.xsd", "xhtml-style-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-inlstyle-1.xsd", "xhtml-inlstyle-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-image-1.xsd", "xhtml-image-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-object-1.xsd", "xhtml-object-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-param-1.xsd", "xhtml-param-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-basic-table-1.xsd", "xhtml-basic-table-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-form-1.xsd", "xhtml-form-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-events-1.xsd", "xhtml-events-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-target-1.xsd", "xhtml-target-1.xsd");
+        m.put("http://www.w3.org/MarkUp/SCHEMA/xhtml-inputmode-1.xsd", "xhtml-inputmode-1.xsd");
+
+        return Maps.transformValues(m, new Function<String, String>() {
+            @Override
+            @Nullable
+            public String apply(@Nullable String input) {
+                return (input == null) ? null : (SCHEMA_PATH + input);
+            }
+        });
+    }
+
 }

src/test/java/org/nuessler/maven/plugin/cakupan/testutil/XsdValidator.java

 
 import java.io.File;
 import java.io.StringReader;
-import java.util.List;
 
 import javax.xml.bind.ValidationException;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 
+import org.xml.sax.EntityResolver;
 import org.xml.sax.ErrorHandler;
 import org.xml.sax.InputSource;
-import org.xml.sax.SAXException;
-import org.xml.sax.SAXParseException;
-
-import com.google.common.collect.Lists;
 
 public class XsdValidator {
     private static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
 
     private final File[] schemas;
     private ErrorHandler errorHandler;
+    private final EntityResolver entityResolver;
 
     public XsdValidator(File... schema) {
+        this(null, schema);
+    }
+
+    public XsdValidator(EntityResolver entityResolver, File... schema) {
+        this.entityResolver = entityResolver;
         this.schemas = schema;
     }
 
         try {
             factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
             factory.setAttribute(JAXP_SCHEMA_SOURCE, schemas);
-            DocumentBuilder documentBuilder = factory.newDocumentBuilder();
-            documentBuilder.setErrorHandler(errorHandler);
-            documentBuilder.parse(new InputSource(new StringReader(xml)));
+            //factory.sets
+            DocumentBuilder builder = factory.newDocumentBuilder();
+            builder.setErrorHandler(errorHandler);
+            builder.setEntityResolver(entityResolver);
+            InputSource input = new InputSource(new StringReader(xml));
+            builder.parse(input);
         } catch (IllegalArgumentException e) {
             throw new ValidationException("XML parser does not seem to support JAXP 1.2");
         } catch (Exception e) {
         }
     }
 
-    private static class CollectingErrorHandler implements ErrorHandler {
-        private final List<SAXParseException> errors = Lists.newArrayList();
-        private final List<SAXParseException> fatalErrors = Lists.newArrayList();
-        private final List<SAXParseException> warnings = Lists.newArrayList();
-
-        @Override
-        public void error(SAXParseException e) throws SAXException {
-            errors.add(e);
-        }
-
-        @Override
-        public void fatalError(SAXParseException e) throws SAXException {
-            fatalErrors.add(e);
-        }
-
-        @Override
-        public void warning(SAXParseException e) throws SAXException {
-            warnings.add(e);
-        }
-
-        public boolean hasErrors() {
-            return !(errors.isEmpty() && fatalErrors.isEmpty());
-        }
-    }
-
 }

src/test/java/org/nuessler/maven/plugin/cakupan/testutil/XslTransformationTestCase.java

 import org.junit.Test;
 import org.junit.rules.ErrorCollector;
 import org.w3c.dom.Document;
+import org.xml.sax.EntityResolver;
 
 public abstract class XslTransformationTestCase {
 
         if (schema != null) {
             String xml = DomUtil.documentToString(transformedXml);
             try {
-                new XsdValidator(schema).validate(xml);
+                new XsdValidator(getEntityResolver(), schema).validate(xml);
             } catch (ValidationException e) {
                 errorCollector.addError(e);
             }
 
     protected abstract void checkResult(Document doc);
 
+    /**
+     * Return the {@link EntityResolver} to be used by the validator. May be
+     * overridden to provide a custom {@link EntityResolver}. May return
+     * <code>null</code> if the default implematation should be used.
+     *
+     * @return the {@link EntityResolver} to be used by the {@link XsdValidator}
+     *         or <code>null</code> if the default implementation should be
+     *         used.
+     */
+    protected EntityResolver getEntityResolver() {
+        return null;
+    }
+
 }