|
| 1 | +/* |
| 2 | + * eXist-db Open Source Native XML Database |
| 3 | + * Copyright (C) 2001 The eXist-db Authors |
| 4 | + * |
| 5 | + * info@exist-db.org |
| 6 | + * http://www.exist-db.org |
| 7 | + * |
| 8 | + * This library is free software; you can redistribute it and/or |
| 9 | + * modify it under the terms of the GNU Lesser General Public |
| 10 | + * License as published by the Free Software Foundation; either |
| 11 | + * version 2.1 of the License, or (at your option) any later version. |
| 12 | + * |
| 13 | + * This library is distributed in the hope that it will be useful, |
| 14 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 16 | + * Lesser General Public License for more details. |
| 17 | + * |
| 18 | + * You should have received a copy of the GNU Lesser General Public |
| 19 | + * License along with this library; if not, write to the Free Software |
| 20 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 21 | + */ |
| 22 | +package org.exist.xquery.functions.validate; |
| 23 | + |
| 24 | +import org.custommonkey.xmlunit.exceptions.XpathException; |
| 25 | +import org.exist.test.ExistXmldbEmbeddedServer; |
| 26 | +import org.exist.util.io.InputStreamUtil; |
| 27 | +import org.junit.*; |
| 28 | + |
| 29 | +import static org.exist.collections.CollectionConfiguration.DEFAULT_COLLECTION_CONFIG_FILE; |
| 30 | +import static org.exist.samples.Samples.SAMPLES; |
| 31 | +import static org.junit.Assert.*; |
| 32 | +import static org.custommonkey.xmlunit.XMLAssert.assertXpathEvaluatesTo; |
| 33 | + |
| 34 | +import java.io.IOException; |
| 35 | +import java.io.InputStream; |
| 36 | + |
| 37 | +import org.xml.sax.SAXException; |
| 38 | +import org.xmldb.api.base.Collection; |
| 39 | +import org.xmldb.api.base.ResourceSet; |
| 40 | +import org.xmldb.api.base.XMLDBException; |
| 41 | + |
| 42 | +/** |
| 43 | + * The {@code tournament/1.5} sample fixtures ship an XSD, an RNG, and a Schematron schema side by |
| 44 | + * side, all describing the same {@code Tournament} document shape. Their own embedded comment |
| 45 | + * explains why: {@code Tournament-valid.xml} and {@code Tournament-invalid.xml} only differ in a |
| 46 | + * co-occurrence constraint -- for a {@code Singles} tournament, {@code nbrParticipants} must equal |
| 47 | + * {@code nbrTeams} -- that neither RELAX NG nor W3C XML Schema can express on their own. Schematron |
| 48 | + * rules are embedded in both {@code Tournament.xsd} (via {@code xsd:appinfo}) and {@code |
| 49 | + * Tournament.rng} (via RELAX NG annotations) specifically to add that missing constraint, but {@code |
| 50 | + * validation:jaxp()}/{@code validation:jing()} only enforce the structural grammar, not embedded |
| 51 | + * Schematron annotations. {@link JingSchematronTest} validates the same pair against {@code |
| 52 | + * tournament-schema.sch} and correctly reports the second document as invalid. |
| 53 | + * |
| 54 | + * <p>Bare RNG bears this out directly: both documents are structurally valid against {@code |
| 55 | + * Tournament.rng} below, since it doesn't express the co-occurrence constraint either.</p> |
| 56 | + * |
| 57 | + * <p>Bare XSD does not -- both documents are structurally <em>invalid</em> against {@code |
| 58 | + * Tournament.xsd}, but for an unrelated reason: {@code Match} {@code m3} references a {@code Team} |
| 59 | + * {@code t5} that is never declared in {@code Teams} (only {@code t1}/{@code t2}/{@code t3} exist). |
| 60 | + * This is a pre-existing defect in this 2001-vintage third-party sample, present identically in both |
| 61 | + * documents -- caught by XSD's built-in {@code xsd:ID}/{@code xsd:IDREF} referential-integrity check |
| 62 | + * (which this {@code Tournament.rng} does not declare for the same elements), not by anything related |
| 63 | + * to the documented co-occurrence constraint. The two XSD tests below assert that both documents fail |
| 64 | + * with the exact same error, which is itself the point: XSD's verdict does not change based on whether |
| 65 | + * {@code nbrParticipants} matches {@code nbrTeams}, confirming it can't see that constraint either.</p> |
| 66 | + */ |
| 67 | +public class TournamentSchemaLanguageComparisonTest { |
| 68 | + |
| 69 | + private static final String[] TEST_RESOURCES = |
| 70 | + { "Tournament-valid.xml", "Tournament-invalid.xml", "Tournament.xsd", "Tournament.rng" }; |
| 71 | + |
| 72 | + @ClassRule |
| 73 | + public static final ExistXmldbEmbeddedServer existEmbeddedServer = new ExistXmldbEmbeddedServer(false, true, true); |
| 74 | + |
| 75 | + private static final String noValidation = "<?xml version='1.0'?>" + |
| 76 | + "<collection xmlns='http://exist-db.org/collection-config/1.0'>" + |
| 77 | + " <validation mode='no'/>" + |
| 78 | + "</collection>"; |
| 79 | + |
| 80 | + @BeforeClass |
| 81 | + public static void prepareResources() throws Exception { |
| 82 | + |
| 83 | + // Switch off validation |
| 84 | + try (Collection conf = existEmbeddedServer.createCollection(existEmbeddedServer.getRoot(), "system/config/db/tournament")) { |
| 85 | + ExistXmldbEmbeddedServer.storeResource(conf, DEFAULT_COLLECTION_CONFIG_FILE, noValidation.getBytes()); |
| 86 | + } |
| 87 | + |
| 88 | + try (Collection col15 = existEmbeddedServer.createCollection(existEmbeddedServer.getRoot(), "tournament/1.5")) { |
| 89 | + for (final String testResource : TEST_RESOURCES) { |
| 90 | + try (final InputStream is = SAMPLES.getSample("validation/tournament/1.5/" + testResource)) { |
| 91 | + assertNotNull(is); |
| 92 | + ExistXmldbEmbeddedServer.storeResource(col15, testResource, InputStreamUtil.readAll(is)); |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + } |
| 97 | + |
| 98 | + @Test |
| 99 | + public void xsdStructureRejectsValidDocumentOnUnrelatedIdrefDefect() throws XMLDBException, SAXException, XpathException, IOException { |
| 100 | + // No xsi:schemaLocation hint on the instance -- resolved purely by Tournament.xsd's |
| 101 | + // targetNamespace via directory-search, the same mechanism JaxpXsdCatalogTest's |
| 102 | + // xsd_searched_* tests use. See the class javadoc for why this is "invalid". |
| 103 | + executeAndEvaluateMessage("validation:jaxp-report( doc('/db/tournament/1.5/Tournament-valid.xml'), false(), " + |
| 104 | + "xs:anyURI('/db/tournament/1.5/') )", "invalid", |
| 105 | + "cvc-id.1: There is no ID/IDREF binding for IDREF 't5'."); |
| 106 | + } |
| 107 | + |
| 108 | + @Test |
| 109 | + public void xsdStructureRejectsCoOccurrenceViolatingDocumentIdentically() throws XMLDBException, SAXException, XpathException, IOException { |
| 110 | + // Bare XSD structural validation cannot see the Singles/nbrParticipants-vs-nbrTeams |
| 111 | + // co-occurrence constraint -- proven here by getting the exact same verdict and error as |
| 112 | + // the "valid" document above, despite the co-occurrence violation. Only the accompanying |
| 113 | + // Schematron rules (tested separately in JingSchematronTest) catch that constraint. |
| 114 | + executeAndEvaluateMessage("validation:jaxp-report( doc('/db/tournament/1.5/Tournament-invalid.xml'), false(), " + |
| 115 | + "xs:anyURI('/db/tournament/1.5/') )", "invalid", |
| 116 | + "cvc-id.1: There is no ID/IDREF binding for IDREF 't5'."); |
| 117 | + } |
| 118 | + |
| 119 | + @Test |
| 120 | + public void rngStructureAcceptsValidDocument() throws XMLDBException, SAXException, XpathException, IOException { |
| 121 | + executeAndEvaluate("validation:jing-report( doc('/db/tournament/1.5/Tournament-valid.xml'), " + |
| 122 | + "doc('/db/tournament/1.5/Tournament.rng') )", "valid"); |
| 123 | + } |
| 124 | + |
| 125 | + @Test |
| 126 | + public void rngStructureAcceptsCoOccurrenceViolatingDocument() throws XMLDBException, SAXException, XpathException, IOException { |
| 127 | + // Same co-occurrence limitation as the XSD case above, for RELAX NG. |
| 128 | + executeAndEvaluate("validation:jing-report( doc('/db/tournament/1.5/Tournament-invalid.xml'), " + |
| 129 | + "doc('/db/tournament/1.5/Tournament.rng') )", "valid"); |
| 130 | + } |
| 131 | + |
| 132 | + private void executeAndEvaluate(final String query, final String expectedValue) throws XMLDBException, SAXException, IOException, XpathException { |
| 133 | + final ResourceSet results = existEmbeddedServer.executeQuery(query); |
| 134 | + assertEquals(1, results.getSize()); |
| 135 | + |
| 136 | + final String r = (String) results.getResource(0).getContent(); |
| 137 | + assertXpathEvaluatesTo(expectedValue, "//status/text()", r); |
| 138 | + } |
| 139 | + |
| 140 | + private void executeAndEvaluateMessage(final String query, final String expectedValue, final String expectedMessage) |
| 141 | + throws XMLDBException, SAXException, IOException, XpathException { |
| 142 | + final ResourceSet results = existEmbeddedServer.executeQuery(query); |
| 143 | + assertEquals(1, results.getSize()); |
| 144 | + |
| 145 | + final String r = (String) results.getResource(0).getContent(); |
| 146 | + assertXpathEvaluatesTo(expectedValue, "//status/text()", r); |
| 147 | + assertXpathEvaluatesTo(expectedMessage, "//message/text()", r); |
| 148 | + } |
| 149 | +} |
0 commit comments