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