1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  package com.helger.schematron;
18  
19  import java.io.IOException;
20  
21  import javax.annotation.Nonnull;
22  import javax.annotation.Nullable;
23  import javax.annotation.concurrent.Immutable;
24  import javax.xml.transform.Source;
25  import javax.xml.transform.dom.DOMSource;
26  
27  import org.oclc.purl.dsdl.svrl.SchematronOutputType;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  import org.w3c.dom.Node;
31  import org.xml.sax.InputSource;
32  
33  import com.helger.commons.ValueEnforcer;
34  import com.helger.commons.annotation.PresentForCodeCoverage;
35  import com.helger.commons.error.list.ErrorList;
36  import com.helger.commons.error.list.IErrorList;
37  import com.helger.commons.hierarchy.visit.ChildrenProviderHierarchyVisitor;
38  import com.helger.commons.hierarchy.visit.DefaultHierarchyVisitorCallback;
39  import com.helger.commons.hierarchy.visit.EHierarchyVisitorReturn;
40  import com.helger.commons.io.resource.IReadableResource;
41  import com.helger.commons.state.ESuccess;
42  import com.helger.commons.wrapper.Wrapper;
43  import com.helger.schematron.pure.errorhandler.IPSErrorHandler;
44  import com.helger.schematron.pure.errorhandler.LoggingPSErrorHandler;
45  import com.helger.schematron.resolve.DefaultSchematronIncludeResolver;
46  import com.helger.schematron.svrl.SVRLFailedAssert;
47  import com.helger.schematron.svrl.SVRLHelper;
48  import com.helger.schematron.svrl.SVRLResourceError;
49  import com.helger.xml.microdom.IMicroDocument;
50  import com.helger.xml.microdom.IMicroElement;
51  import com.helger.xml.microdom.IMicroNode;
52  import com.helger.xml.microdom.serialize.MicroReader;
53  import com.helger.xml.sax.InputSourceFactory;
54  import com.helger.xml.serialize.read.ISAXReaderSettings;
55  
56  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
57  
58  
59  
60  
61  
62  
63  
64  @Immutable
65  public final class SchematronHelper
66  {
67    private static final Logger LOGGER = LoggerFactory.getLogger (SchematronHelper.class);
68  
69    @PresentForCodeCoverage
70    private static final SchematronHelperhematronHelper">SchematronHelper s_aInstance = new SchematronHelper ();
71  
72    private SchematronHelper ()
73    {}
74  
75    
76  
77  
78  
79  
80  
81  
82  
83  
84  
85  
86  
87  
88    @Nullable
89    public static SchematronOutputType applySchematron (@Nonnull final ISchematronResource aSchematron,
90                                                        @Nonnull final IReadableResource aXML)
91    {
92      ValueEnforcer.notNull (aSchematron, "SchematronResource");
93      ValueEnforcer.notNull (aXML, "XMLSource");
94  
95      try
96      {
97        
98        return aSchematron.applySchematronValidationToSVRL (aXML);
99      }
100     catch (final Exception ex)
101     {
102       throw new IllegalArgumentException ("Failed to apply Schematron " +
103                                           aSchematron.getID () +
104                                           " onto XML resource " +
105                                           aXML.getResourceID (),
106                                           ex);
107     }
108   }
109 
110   
111 
112 
113 
114 
115 
116 
117 
118 
119 
120 
121 
122   @Nullable
123   public static SchematronOutputType applySchematron (@Nonnull final ISchematronResource aSchematron,
124                                                       @Nonnull final Source aXML)
125   {
126     ValueEnforcer.notNull (aSchematron, "SchematronResource");
127     ValueEnforcer.notNull (aXML, "XMLSource");
128 
129     try
130     {
131       
132       return aSchematron.applySchematronValidationToSVRL (aXML);
133     }
134     catch (final Exception ex)
135     {
136       throw new IllegalArgumentException ("Failed to apply Schematron " +
137                                           aSchematron.getID () +
138                                           " onto XML source " +
139                                           aXML,
140                                           ex);
141     }
142   }
143 
144   
145 
146 
147 
148 
149 
150 
151 
152 
153 
154 
155 
156   @Nullable
157   public static SchematronOutputType applySchematron (@Nonnull final ISchematronResource aSchematron,
158                                                       @Nonnull final Node aNode)
159   {
160     ValueEnforcer.notNull (aSchematron, "SchematronResource");
161     ValueEnforcer.notNull (aNode, "Node");
162 
163     return applySchematron (aSchematron, new DOMSource (aNode));
164   }
165 
166   
167 
168 
169 
170 
171 
172 
173 
174 
175 
176 
177   @Nonnull
178   public static IErrorList convertToErrorList (@Nonnull final SchematronOutputType aSchematronOutput,
179                                                @Nullable final String sResourceName)
180   {
181     ValueEnforcer.notNull (aSchematronOutput, "SchematronOutput");
182 
183     final ErrorList ret = new ErrorList ();
184     for (final SVRLFailedAssert aFailedAssert : SVRLHelper.getAllFailedAssertions (aSchematronOutput))
185       ret.add (aFailedAssert.getAsResourceError (sResourceName));
186     return ret;
187   }
188 
189   @SuppressFBWarnings ("RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE")
190   @Nonnull
191   private static ESuccess _recursiveResolveAllSchematronIncludes (@Nonnull final IMicroElement eRoot,
192                                                                   @Nonnull final IReadableResource aResource,
193                                                                   @Nullable final ISAXReaderSettings aSettings,
194                                                                   @Nonnull final IPSErrorHandler aErrorHandler)
195   {
196     if (eRoot != null)
197     {
198       final DefaultSchematronIncludeResolver#DefaultSchematronIncludeResolver">DefaultSchematronIncludeResolver aIncludeResolver = new DefaultSchematronIncludeResolver (aResource);
199 
200       for (final IMicroElement aElement : eRoot.getAllChildElementsRecursive ())
201         if (CSchematron.NAMESPACE_SCHEMATRON.equals (aElement.getNamespaceURI ()) &&
202             aElement.getLocalName ().equals (CSchematronXML.ELEMENT_INCLUDE))
203         {
204           String sHref = aElement.getAttributeValue (CSchematronXML.ATTR_HREF);
205           try
206           {
207             final int nHashIndex = sHref.indexOf ('#');
208             String sAnchor = null;
209             if (nHashIndex >= 0)
210             {
211               sAnchor = sHref.substring (nHashIndex + 1);
212               sHref = sHref.substring (0, nHashIndex);
213             }
214 
215             final IReadableResource aIncludeRes = aIncludeResolver.getResolvedSchematronResource (sHref);
216             if (aIncludeRes == null)
217             {
218               aErrorHandler.error (aResource, null, "Failed to resolve include '" + sHref + "'", null);
219               return ESuccess.FAILURE;
220             }
221 
222             if (LOGGER.isDebugEnabled ())
223               LOGGER.debug ("Resolved '" +
224                                sHref +
225                                "' relative to '" +
226                                aIncludeResolver.getBaseHref () +
227                                "' as '" +
228                                aIncludeRes.getPath () +
229                                "'");
230 
231             
232             final IMicroDocument aIncludedDoc = MicroReader.readMicroXML (aIncludeRes, aSettings);
233             if (aIncludedDoc == null)
234             {
235               aErrorHandler.error (aResource, null, "Failed to parse include " + aIncludeRes, null);
236               return ESuccess.FAILURE;
237             }
238 
239             IMicroElement aIncludedContent;
240             if (sAnchor == null)
241             {
242               
243 
244               
245               aIncludedContent = aIncludedDoc.getDocumentElement ();
246             }
247             else
248             {
249               final String sFinalAnchor = sAnchor;
250               final Wrapper <IMicroElement> aMatch = new Wrapper <> ();
251               
252               ChildrenProviderHierarchyVisitor.visitFrom (aIncludedDoc.getDocumentElement (),
253                                                           new DefaultHierarchyVisitorCallback <IMicroNode> ()
254                                                           {
255                                                             @Override
256                                                             public EHierarchyVisitorReturn onItemBeforeChildren (final IMicroNode aItem)
257                                                             {
258                                                               if (aItem.isElement ())
259                                                               {
260                                                                 final IMicroElement aCurElement = (IMicroElement) aItem;
261                                                                 final String sID = aCurElement.getAttributeValue ("id");
262                                                                 if (sFinalAnchor.equals (sID))
263                                                                   aMatch.set (aCurElement);
264                                                               }
265                                                               return EHierarchyVisitorReturn.CONTINUE;
266                                                             }
267                                                           },
268                                                           true);
269               aIncludedContent = aMatch.get ();
270               if (aIncludedContent == null)
271               {
272                 aErrorHandler.warn (aResource,
273                                     null,
274                                     "Failed to resolve an element with the ID '" +
275                                           sAnchor +
276                                           "' in " +
277                                           aIncludeRes +
278                                           "! Therefore including the whole document!");
279                 aIncludedContent = aIncludedDoc.getDocumentElement ();
280               }
281             }
282 
283             
284             aIncludedContent.detachFromParent ();
285 
286             
287             if (!CSchematron.NAMESPACE_SCHEMATRON.equals (aIncludedContent.getNamespaceURI ()))
288             {
289               aErrorHandler.error (aResource,
290                                    null,
291                                    "The included resource " +
292                                          aIncludeRes +
293                                          " contains the wrong XML namespace URI '" +
294                                          aIncludedContent.getNamespaceURI () +
295                                          "' but was expected to have '" +
296                                          CSchematron.NAMESPACE_SCHEMATRON +
297                                          "'",
298                                    null);
299               return ESuccess.FAILURE;
300             }
301 
302             
303             if (CSchematronXML.ELEMENT_SCHEMA.equals (aIncludedContent.getLocalName ()))
304             {
305               aErrorHandler.warn (aResource,
306                                   null,
307                                   "The included resource " +
308                                         aIncludeRes +
309                                         " seems to be a complete schema. To includes parts of a schema the respective element must be the root element of the included resource.");
310             }
311 
312             
313             if (_recursiveResolveAllSchematronIncludes (aIncludedContent,
314                                                         aIncludeRes,
315                                                         aSettings,
316                                                         aErrorHandler).isFailure ())
317               return ESuccess.FAILURE;
318 
319             
320             aElement.getParent ().replaceChild (aElement, aIncludedContent);
321           }
322           catch (final IOException ex)
323           {
324             aErrorHandler.error (aResource, null, "Failed to read include '" + sHref + "'", ex);
325             return ESuccess.FAILURE;
326           }
327         }
328     }
329     return ESuccess.SUCCESS;
330   }
331 
332   
333 
334 
335 
336 
337 
338 
339 
340   @Nullable
341   public static IMicroDocument getWithResolvedSchematronIncludes (@Nonnull final IReadableResource aResource)
342   {
343     return getWithResolvedSchematronIncludes (aResource, (ISAXReaderSettings) null, new LoggingPSErrorHandler ());
344   }
345 
346   
347 
348 
349 
350 
351 
352 
353 
354 
355 
356 
357 
358 
359   @Nullable
360   public static IMicroDocument getWithResolvedSchematronIncludes (@Nonnull final IReadableResource aResource,
361                                                                   @Nullable final ISAXReaderSettings aSettings,
362                                                                   @Nonnull final IPSErrorHandler aErrorHandler)
363   {
364     final InputSource aIS = InputSourceFactory.create (aResource);
365     final IMicroDocument aDoc = MicroReader.readMicroXML (aIS, aSettings);
366     if (aDoc != null)
367     {
368       
369       if (_recursiveResolveAllSchematronIncludes (aDoc.getDocumentElement (),
370                                                   aResource,
371                                                   aSettings,
372                                                   aErrorHandler).isFailure ())
373       {
374         
375         return null;
376       }
377     }
378     return aDoc;
379   }
380 }