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