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.DefaultHierarchyVisitorCallback;
37 import com.helger.commons.hierarchy.visit.EHierarchyVisitorReturn;
38 import com.helger.commons.io.resource.IReadableResource;
39 import com.helger.commons.microdom.IMicroDocument;
40 import com.helger.commons.microdom.IMicroElement;
41 import com.helger.commons.microdom.IMicroNode;
42 import com.helger.commons.microdom.serialize.MicroReader;
43 import com.helger.commons.microdom.util.MicroVisitor;
44 import com.helger.commons.state.ESuccess;
45 import com.helger.commons.wrapper.Wrapper;
46 import com.helger.commons.xml.serialize.read.ISAXReaderSettings;
47 import com.helger.schematron.pure.errorhandler.IPSErrorHandler;
48 import com.helger.schematron.pure.errorhandler.LoggingPSErrorHandler;
49 import com.helger.schematron.resolve.DefaultSchematronIncludeResolver;
50 import com.helger.schematron.svrl.SVRLFailedAssert;
51 import com.helger.schematron.svrl.SVRLHelper;
52 import com.helger.schematron.svrl.SVRLResourceError;
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 <IMicroElement> ();
249 MicroVisitor.visit (aIncludedDoc.getDocumentElement (),
250 new DefaultHierarchyVisitorCallback <IMicroNode> ()
251 {
252 @Override
253 public EHierarchyVisitorReturn onItemBeforeChildren (final IMicroNode aItem)
254 {
255 if (aItem.isElement ())
256 {
257 final IMicroElement aCurElement = (IMicroElement) aItem;
258 final String sID = aCurElement.getAttributeValue ("id");
259 if (sFinalAnchor.equals (sID))
260 aMatch.set (aCurElement);
261 }
262 return EHierarchyVisitorReturn.CONTINUE;
263 }
264 });
265 aIncludedContent = aMatch.get ();
266 if (aIncludedContent == null)
267 {
268 aErrorHandler.warn (aResource,
269 null,
270 "Failed to resolve an element with the ID '" +
271 sAnchor +
272 "' in " +
273 aIncludeRes +
274 "! Therefore including the whole document!");
275 aIncludedContent = aIncludedDoc.getDocumentElement ();
276 }
277 }
278
279
280 aIncludedContent.detachFromParent ();
281
282
283 if (!CSchematron.NAMESPACE_SCHEMATRON.equals (aIncludedContent.getNamespaceURI ()))
284 {
285 aErrorHandler.error (aResource,
286 null,
287 "The included resource " +
288 aIncludeRes +
289 " contains the wrong XML namespace URI '" +
290 aIncludedContent.getNamespaceURI () +
291 "' but was expected to have '" +
292 CSchematron.NAMESPACE_SCHEMATRON +
293 "'",
294 null);
295 return ESuccess.FAILURE;
296 }
297
298
299 if (CSchematronXML.ELEMENT_SCHEMA.equals (aIncludedContent.getLocalName ()))
300 {
301 aErrorHandler.warn (aResource,
302 null,
303 "The included resource " +
304 aIncludeRes +
305 " seems to be a complete schema. To includes parts of a schema the respective element must be the root element of the included resource.");
306 }
307
308
309 if (_recursiveResolveAllSchematronIncludes (aIncludedContent,
310 aIncludeRes,
311 aSettings,
312 aErrorHandler).isFailure ())
313 return ESuccess.FAILURE;
314
315
316 aElement.getParent ().replaceChild (aElement, aIncludedContent);
317 }
318 catch (final IOException ex)
319 {
320 aErrorHandler.error (aResource, null, "Failed to read include '" + sHref + "'", ex);
321 return ESuccess.FAILURE;
322 }
323 }
324 }
325 return ESuccess.SUCCESS;
326 }
327
328
329
330
331
332
333
334
335
336 @Nullable
337 public static IMicroDocument getWithResolvedSchematronIncludes (@Nonnull final IReadableResource aResource)
338 {
339 return getWithResolvedSchematronIncludes (aResource, (ISAXReaderSettings) null, new LoggingPSErrorHandler ());
340 }
341
342
343
344
345
346
347
348
349
350
351
352
353 @Nullable
354 @Deprecated
355 public static IMicroDocument getWithResolvedSchematronIncludes (@Nonnull final IReadableResource aResource,
356 @Nullable final ISAXReaderSettings aSettings)
357 {
358 return getWithResolvedSchematronIncludes (aResource, aSettings, new LoggingPSErrorHandler ());
359 }
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374 @Nullable
375 public static IMicroDocument getWithResolvedSchematronIncludes (@Nonnull final IReadableResource aResource,
376 @Nullable final ISAXReaderSettings aSettings,
377 @Nonnull final IPSErrorHandler aErrorHandler)
378 {
379 final IMicroDocument aDoc = MicroReader.readMicroXML (aResource, aSettings);
380 if (aDoc != null)
381 {
382
383 if (_recursiveResolveAllSchematronIncludes (aDoc.getDocumentElement (),
384 aResource,
385 aSettings,
386 aErrorHandler).isFailure ())
387 {
388
389 return null;
390 }
391 }
392 return aDoc;
393 }
394 }