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