View Javadoc
1   /**
2    * Copyright (C) 2014-2018 Philip Helger (www.helger.com)
3    * philip[at]helger[dot]com
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    *         http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package com.helger.schematron.pure;
18  
19  import static org.junit.Assert.assertEquals;
20  import static org.junit.Assert.assertFalse;
21  import static org.junit.Assert.assertNotNull;
22  import static org.junit.Assert.assertTrue;
23  
24  import java.nio.charset.StandardCharsets;
25  import java.util.List;
26  
27  import javax.xml.validation.Schema;
28  
29  import org.junit.Test;
30  import org.oclc.purl.dsdl.svrl.SchematronOutputType;
31  import org.w3c.dom.Document;
32  import org.w3c.dom.Node;
33  
34  import com.helger.commons.io.resource.ClassPathResource;
35  import com.helger.commons.io.resource.IReadableResource;
36  import com.helger.commons.io.stream.StringInputStream;
37  import com.helger.schematron.SchematronException;
38  import com.helger.schematron.pure.errorhandler.CollectingPSErrorHandler;
39  import com.helger.schematron.pure.errorhandler.DoNothingPSErrorHandler;
40  import com.helger.schematron.pure.errorhandler.LoggingPSErrorHandler;
41  import com.helger.schematron.svrl.SVRLHelper;
42  import com.helger.schematron.testfiles.SchematronTestHelper;
43  import com.helger.schematron.xpath.XQueryAsXPathFunctionConverter;
44  import com.helger.xml.schema.XMLSchemaCache;
45  import com.helger.xml.serialize.read.DOMReader;
46  import com.helger.xml.serialize.read.DOMReaderSettings;
47  import com.helger.xml.xpath.MapBasedXPathFunctionResolver;
48  import com.helger.xml.xpath.MapBasedXPathVariableResolver;
49  
50  /**
51   * Test class for class {@link SchematronResourcePure}.
52   *
53   * @author Philip Helger
54   */
55  public final class SchematronResourcePureTest
56  {
57    @Test
58    public void testBasic () throws Exception
59    {
60      for (final IReadableResource aRes : SchematronTestHelper.getAllValidSchematronFiles ())
61      {
62        // The validity is tested in another test case!
63        // Parse them
64        final SchematronResourcePure aResPure = new SchematronResourcePure (aRes);
65        assertTrue (aRes.getPath (), aResPure.isValidSchematron ());
66      }
67    }
68  
69    @Test
70    public void testFromByteArray ()
71    {
72      final String sTest = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" +
73                           "<iso:schema xmlns=\"http://purl.oclc.org/dsdl/schematron\" \n" +
74                           "         xmlns:iso=\"http://purl.oclc.org/dsdl/schematron\" \n" +
75                           "         xmlns:sch=\"http://www.ascc.net/xml/schematron\"\n" +
76                           "         queryBinding='xslt2'\n" +
77                           "         schemaVersion=\"ISO19757-3\">\n" +
78                           "  <iso:title>Test ISO schematron file. Introduction mode</iso:title>\n" +
79                           "  <iso:ns prefix=\"dp\" uri=\"http://www.dpawson.co.uk/ns#\" />\n" +
80                           " <iso:pattern >\n" +
81                           "    <iso:title>A very simple pattern with a title</iso:title>\n" +
82                           "    <iso:rule context=\"chapter\">\n" +
83                           "      <iso:assert test=\"title\">Chapter should have a title</iso:assert>\n" +
84                           "      <iso:report test=\"count(para)\">\n" +
85                           "      <iso:value-of select=\"count(para)\"/> paragraphs</iso:report>\n" +
86                           "    </iso:rule>\n" +
87                           "  </iso:pattern>\n" +
88                           "\n" +
89                           "</iso:schema>";
90      assertTrue (SchematronResourcePure.fromByteArray (sTest.getBytes (StandardCharsets.UTF_8)).isValidSchematron ());
91      assertTrue (SchematronResourcePure.fromInputStream (new StringInputStream (sTest, StandardCharsets.UTF_8))
92                                        .isValidSchematron ());
93    }
94  
95    @Test
96    public void testParseWithXPathError ()
97    {
98      final String sTest = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" +
99                           "<iso:schema xmlns=\"http://purl.oclc.org/dsdl/schematron\" \n" +
100                          "         xmlns:iso=\"http://purl.oclc.org/dsdl/schematron\" \n" +
101                          "         xmlns:sch=\"http://www.ascc.net/xml/schematron\"\n" +
102                          "         queryBinding='xslt2'\n" +
103                          "         schemaVersion=\"ISO19757-3\">\n" +
104                          "  <iso:title>Test ISO schematron file. Introduction mode</iso:title>\n" +
105                          "  <iso:ns prefix=\"dp\" uri=\"http://www.dpawson.co.uk/ns#\" />\n" +
106                          "  <iso:pattern>\n" +
107                          "    <iso:rule context=\"chapter\">\n"
108                          // This line contains the XPath error (Node xor number
109                          // is invalid)
110                          +
111                          "      <iso:assert test=\"title xor 55\">Chapter should have a title</iso:assert>\n"
112                          // This line contains the second XPath error (Node xor
113                          // number is still invalid)
114                          +
115                          "      <iso:assert test=\"title xor 33\">Chapter should have a title</iso:assert>\n" +
116                          "    </iso:rule>\n" +
117                          "  </iso:pattern>\n" +
118                          "</iso:schema>";
119     final CollectingPSErrorHandler aErrorHandler = new CollectingPSErrorHandler ();
120     final SchematronResourcePure aSch = SchematronResourcePure.fromByteArray (sTest.getBytes (StandardCharsets.UTF_8))
121                                                               .setErrorHandler (aErrorHandler);
122     // Perform quick validation
123     assertFalse (aSch.isValidSchematron ());
124     assertEquals ("Expected three errors: " + aErrorHandler.getErrorList ().toString (),
125                   3,
126                   aErrorHandler.getErrorList ().size ());
127     if (false)
128       System.out.println (aErrorHandler.getErrorList ().toString ());
129 
130     // Perform complete validation
131     aErrorHandler.clearResourceErrors ();
132     aSch.validateCompletely ();
133     assertEquals ("Expected three errors: " + aErrorHandler.getErrorList ().toString (),
134                   3,
135                   aErrorHandler.getErrorList ().size ());
136   }
137 
138   @Test
139   public void testResolveVariables () throws SchematronException
140   {
141     final String sTest = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" +
142                          "<iso:schema xmlns=\"http://purl.oclc.org/dsdl/schematron\" \n" +
143                          "         xmlns:iso=\"http://purl.oclc.org/dsdl/schematron\" \n" +
144                          "         xmlns:sch=\"http://www.ascc.net/xml/schematron\"\n" +
145                          "         queryBinding='xslt2'\n" +
146                          "         schemaVersion=\"ISO19757-3\">\n" +
147                          "  <iso:title>Test ISO schematron file. Introduction mode</iso:title>\n" +
148                          "  <iso:ns prefix=\"dp\" uri=\"http://www.dpawson.co.uk/ns#\" />\n" +
149                          "  <iso:ns prefix=\"java\" uri=\"http://helger.com/schematron/test\" />\n" +
150                          "  <iso:pattern >\n" +
151                          "    <iso:title>A very simple pattern with a title</iso:title>\n" +
152                          "    <iso:rule context=\"chapter\">\n"
153                          // Custom variable
154                          +
155                          "      <iso:assert test=\"$title-element\">Chapter should have a title</iso:assert>\n"
156                          // Custom function
157                          +
158                          "      <iso:report test=\"java:my-count(para) = 2\">\n"
159                          // Custom function
160                          +
161                          "      <iso:value-of select=\"java:my-count(para)\"/> paragraphs found</iso:report>\n" +
162                          "    </iso:rule>\n" +
163                          "  </iso:pattern>\n" +
164                          "\n" +
165                          "</iso:schema>";
166 
167     // Test without variable and function resolver
168     // -> an error is expected, but we don't need to log it
169     assertFalse (SchematronResourcePure.fromString (sTest, StandardCharsets.UTF_8)
170                                        .setErrorHandler (new DoNothingPSErrorHandler ())
171                                        .isValidSchematron ());
172 
173     // Test with variable and function resolver
174     final MapBasedXPathVariableResolver aVarResolver = new MapBasedXPathVariableResolver ();
175     aVarResolver.addUniqueVariable ("title-element", "title");
176 
177     final MapBasedXPathFunctionResolver aFunctionResolver = new MapBasedXPathFunctionResolver ();
178     aFunctionResolver.addUniqueFunction ("http://helger.com/schematron/test", "my-count", 1, args -> {
179       final List <?> aArg = (List <?>) args.get (0);
180       return Integer.valueOf (aArg.size ());
181     });
182     final Document aTestDoc = DOMReader.readXMLDOM ("<?xml version='1.0'?><chapter><title /><para>First para</para><para>Second para</para></chapter>");
183     final SchematronOutputType aOT = SchematronResourcePure.fromString (sTest, StandardCharsets.UTF_8)
184                                                            .setVariableResolver (aVarResolver)
185                                                            .setFunctionResolver (aFunctionResolver)
186                                                            .applySchematronValidationToSVRL (aTestDoc, null);
187     assertNotNull (aOT);
188     assertEquals (0, SVRLHelper.getAllFailedAssertions (aOT).size ());
189     assertEquals (1, SVRLHelper.getAllSuccessfulReports (aOT).size ());
190     // Note: the text contains all whitespaces!
191     assertEquals ("\n      2 paragraphs found".trim (), SVRLHelper.getAllSuccessfulReports (aOT).get (0).getText ());
192   }
193 
194   @Test
195   public void testResolveFunctions () throws SchematronException
196   {
197     final String sTest = "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>\n" +
198                          "<iso:schema xmlns=\"http://purl.oclc.org/dsdl/schematron\" \n" +
199                          "         xmlns:iso=\"http://purl.oclc.org/dsdl/schematron\" \n" +
200                          "         xmlns:sch=\"http://www.ascc.net/xml/schematron\"\n" +
201                          "         queryBinding='xslt2'\n" +
202                          "         schemaVersion=\"ISO19757-3\">\n" +
203                          "  <iso:title>Test ISO schematron file. Introduction mode</iso:title>\n" +
204                          "  <iso:ns prefix=\"dp\" uri=\"http://www.dpawson.co.uk/ns#\" />\n" +
205                          "  <iso:ns prefix=\"java\" uri=\"http://helger.com/schematron/test\" />\n" +
206                          "  <iso:pattern >\n" +
207                          "    <iso:title>A very simple pattern with a title</iso:title>\n" +
208                          "    <iso:rule context=\"chapter\">\n" +
209                          "      <iso:assert test=\"title\">Chapter should have a title</iso:assert>\n" +
210                          "      <iso:report test=\"count(para) = 2\">\n"
211                          // Custom function
212                          +
213                          "      Node details: <iso:value-of select=\"java:get-nodelist-details(para)\"/> - end</iso:report>\n" +
214                          "    </iso:rule>\n" +
215                          "  </iso:pattern>\n" +
216                          "\n" +
217                          "</iso:schema>";
218 
219     // Test with variable and function resolver
220     final MapBasedXPathFunctionResolver aFunctionResolver = new MapBasedXPathFunctionResolver ();
221     aFunctionResolver.addUniqueFunction ("http://helger.com/schematron/test", "get-nodelist-details", 1, args -> {
222       // We expect exactly one argument
223       assertEquals (1, args.size ());
224       // The type of the first argument
225       // itself is also a list
226       final List <?> aFirstArg = (List <?>) args.get (0);
227       // Ensure that the first argument
228       // only contains Nodes
229       final StringBuilder ret = new StringBuilder ();
230       boolean bFirst = true;
231       for (final Object aFirstArgItem : aFirstArg)
232       {
233         assertTrue (aFirstArgItem instanceof Node);
234         final Node aNode = (Node) aFirstArgItem;
235 
236         if (bFirst)
237           bFirst = false;
238         else
239           ret.append (", ");
240 
241         ret.append (aNode.getNodeName ()).append ("[").append (aNode.getTextContent ()).append ("]");
242       }
243 
244       return ret;
245     });
246 
247     final Document aTestDoc = DOMReader.readXMLDOM ("<?xml version='1.0'?>" +
248                                                     "<chapter>" +
249                                                     "<title />" +
250                                                     "<para>First para</para>" +
251                                                     "<para>Second para</para>" +
252                                                     "</chapter>");
253     final SchematronOutputType aOT = SchematronResourcePure.fromString (sTest, StandardCharsets.UTF_8)
254                                                            .setFunctionResolver (aFunctionResolver)
255                                                            .applySchematronValidationToSVRL (aTestDoc, null);
256     assertNotNull (aOT);
257     assertEquals (0, SVRLHelper.getAllFailedAssertions (aOT).size ());
258     assertEquals (1, SVRLHelper.getAllSuccessfulReports (aOT).size ());
259     // Note: the text contains all whitespaces!
260     assertEquals ("\n      Node details: para[First para], para[Second para] - end".trim (),
261                   SVRLHelper.getAllSuccessfulReports (aOT).get (0).getText ());
262   }
263 
264   @Test
265   public void testResolveXQueryFunctions () throws Exception
266   {
267     final String sTest = "<?xml version='1.0' encoding='iso-8859-1'?>\n" +
268                          "<iso:schema xmlns='http://purl.oclc.org/dsdl/schematron' \n" +
269                          "         xmlns:iso='http://purl.oclc.org/dsdl/schematron' \n" +
270                          "         xmlns:sch='http://www.ascc.net/xml/schematron'\n" +
271                          "         queryBinding='xslt2'\n" +
272                          "         schemaVersion='ISO19757-3'>\n" +
273                          "  <iso:title>Test ISO schematron file. Introduction mode</iso:title>\n" +
274                          "  <iso:ns prefix='dp' uri='http://www.dpawson.co.uk/ns#' />\n" +
275                          "  <iso:ns prefix='functx' uri='http://www.functx.com' />\n" +
276                          "  <iso:pattern >\n" +
277                          "    <iso:title>A very simple pattern with a title</iso:title>\n" +
278                          "    <iso:rule context='chapter'>\n" +
279                          "      <iso:assert test='title'>Chapter should have a title</iso:assert>\n" +
280                          "      <iso:report test='count(para) = 2'>\n"
281                          // Custom function
282                          +
283                          "      Node kind: <iso:value-of select='functx:node-kind(para)'/> - end</iso:report>\n" +
284                          "    </iso:rule>\n" +
285                          "  </iso:pattern>\n" +
286                          "\n" +
287                          "</iso:schema>";
288 
289     final MapBasedXPathFunctionResolver aFunctionResolver = new XQueryAsXPathFunctionConverter ().loadXQuery (ClassPathResource.getInputStream ("xquery/functx-1.0-nodoc-2007-01.xq"));
290 
291     // Test with variable and function resolver
292     final Document aTestDoc = DOMReader.readXMLDOM ("<?xml version='1.0'?>" +
293                                                     "<chapter>" +
294                                                     "<title />" +
295                                                     "<para>First para</para>" +
296                                                     "<para>Second para</para>" +
297                                                     "</chapter>");
298     final SchematronOutputType aOT = SchematronResourcePure.fromString (sTest, StandardCharsets.UTF_8)
299                                                            .setFunctionResolver (aFunctionResolver)
300                                                            .applySchematronValidationToSVRL (aTestDoc, null);
301     assertNotNull (aOT);
302     assertEquals (0, SVRLHelper.getAllFailedAssertions (aOT).size ());
303     assertEquals (1, SVRLHelper.getAllSuccessfulReports (aOT).size ());
304     // Note: the text contains all whitespaces!
305     assertEquals ("\n      Node kind: element - end".trim (),
306                   SVRLHelper.getAllSuccessfulReports (aOT).get (0).getText ());
307   }
308 
309   @Test
310   public void testResolveFunctXAreDistinctValuesQueryFunctions () throws Exception
311   {
312     final String sTest = "<?xml version='1.0' encoding='iso-8859-1'?>\n" +
313                          "<iso:schema xmlns='http://purl.oclc.org/dsdl/schematron' \n" +
314                          "         xmlns:iso='http://purl.oclc.org/dsdl/schematron' \n" +
315                          "         xmlns:sch='http://www.ascc.net/xml/schematron'\n" +
316                          "         queryBinding='xslt2'\n" +
317                          "         schemaVersion='ISO19757-3'>\n" +
318                          "  <iso:title>Test ISO schematron file. Introduction mode</iso:title>\n" +
319                          "  <iso:ns prefix='dp' uri='http://www.dpawson.co.uk/ns#' />\n" +
320                          "  <iso:ns prefix='fn' uri='http://www.w3.org/2005/xpath-functions' />\n" +
321                          "  <iso:ns prefix='functx' uri='http://www.functx.com' />\n" +
322                          "    <iso:pattern >\n" +
323                          "    <iso:title>A very simple pattern with a title</iso:title>\n" +
324                          "    <iso:rule context='chapter'>\n" +
325                          "      <iso:assert test='functx:are-distinct-values(para)'>Should have distinct values</iso:assert>\n" +
326                          "      </iso:rule>\n" +
327                          "  </iso:pattern>\n" +
328                          "</iso:schema>";
329 
330     final MapBasedXPathFunctionResolver aFunctionResolver = new XQueryAsXPathFunctionConverter ().loadXQuery (ClassPathResource.getInputStream ("xquery/functx-1.0-nodoc-2007-01.xq"));
331 
332     // Test with variable and function resolver
333     final Document aTestDoc = DOMReader.readXMLDOM ("<?xml version='1.0'?>" +
334                                                     "<chapter>" +
335                                                     "<title />" +
336                                                     "<para>100</para>" +
337                                                     "<para>200</para>" +
338                                                     "</chapter>");
339     final CollectingPSErrorHandler aErrorHandler = new CollectingPSErrorHandler (new LoggingPSErrorHandler ());
340     final SchematronOutputType aOT = SchematronResourcePure.fromString (sTest, StandardCharsets.UTF_8)
341                                                            .setFunctionResolver (aFunctionResolver)
342                                                            .setErrorHandler (aErrorHandler)
343                                                            .applySchematronValidationToSVRL (aTestDoc, null);
344     assertNotNull (aOT);
345     // XXX fails :(
346     if (false)
347       assertTrue (aErrorHandler.getAllErrors ().toString (), aErrorHandler.isEmpty ());
348     assertEquals (0, SVRLHelper.getAllFailedAssertions (aOT).size ());
349   }
350 
351   @Test
352   public void testFunctXAreDistinctValuesWithXSD () throws Exception
353   {
354     final String sTest = "<?xml version='1.0' encoding='iso-8859-1'?>\n" +
355                          "<schema xmlns='http://purl.oclc.org/dsdl/schematron'>\n" +
356                          "  <ns prefix=\"xs\" uri=\"http://www.w3.org/2001/XMLSchema\"/>\n" +
357                          "  <ns prefix='fn' uri='http://www.w3.org/2005/xpath-functions' />\n" +
358                          "  <ns prefix='functx' uri='http://www.functx.com' />\n" +
359                          "  <pattern name='toto'>\n" +
360                          "    <title>A very simple pattern with a title</title>\n" +
361                          "    <rule context='chapter'>\n" +
362                          "      <assert test='fn:count(fn:distinct-values(para)) = fn:count(para)'>Should have distinct values</assert>\n" +
363                          "      </rule>\n" +
364                          "  </pattern>\n" +
365                          "</schema>";
366 
367     final MapBasedXPathFunctionResolver aFunctionResolver = new XQueryAsXPathFunctionConverter ().loadXQuery (ClassPathResource.getInputStream ("xquery/functx-1.0-nodoc-2007-01.xq"));
368 
369     final Schema aSchema = XMLSchemaCache.getInstance ()
370                                          .getSchema (new ClassPathResource ("issues/20141124/chapter.xsd"));
371     final Document aTestDoc = DOMReader.readXMLDOM ("<?xml version='1.0'?>" +
372                                                     "<chapter>" +
373                                                     " <title />" +
374                                                     " <para>09</para>" +
375                                                     " <para>9</para>" +
376                                                     "</chapter>",
377                                                     new DOMReaderSettings ().setSchema (aSchema));
378 
379     final SchematronOutputType aOT = SchematronResourcePure.fromString (sTest, StandardCharsets.UTF_8)
380                                                            .setFunctionResolver (aFunctionResolver)
381                                                            .applySchematronValidationToSVRL (aTestDoc, null);
382     assertNotNull (aOT);
383     if (SVRLHelper.getAllFailedAssertions (aOT).size () != 0)
384     {
385       System.out.println (SVRLHelper.getAllFailedAssertions (aOT).get (0).getText ());
386     }
387     assertTrue (SVRLHelper.getAllFailedAssertions (aOT).isEmpty ());
388   }
389 }