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 s_aLogger = LoggerFactory.getLogger (SchematronHelper.class);
68
69 @PresentForCodeCoverage
70 private static final 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 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 (s_aLogger.isDebugEnabled ())
223 s_aLogger.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 }