1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.helger.schematron.pure.validation.xpath;
18
19 import java.util.List;
20 import java.util.Map;
21
22 import javax.annotation.Nonnull;
23 import javax.annotation.Nullable;
24 import javax.annotation.concurrent.NotThreadSafe;
25 import javax.xml.xpath.XPathConstants;
26 import javax.xml.xpath.XPathExpressionException;
27
28 import org.oclc.purl.dsdl.svrl.ActivePattern;
29 import org.oclc.purl.dsdl.svrl.DiagnosticReference;
30 import org.oclc.purl.dsdl.svrl.FailedAssert;
31 import org.oclc.purl.dsdl.svrl.FiredRule;
32 import org.oclc.purl.dsdl.svrl.NsPrefixInAttributeValues;
33 import org.oclc.purl.dsdl.svrl.SchematronOutputType;
34 import org.oclc.purl.dsdl.svrl.SuccessfulReport;
35 import org.w3c.dom.Node;
36
37 import com.helger.commons.ValueEnforcer;
38 import com.helger.commons.collection.CollectionHelper;
39 import com.helger.commons.state.EContinue;
40 import com.helger.schematron.pure.bound.xpath.PSXPathBoundAssertReport;
41 import com.helger.schematron.pure.bound.xpath.PSXPathBoundDiagnostic;
42 import com.helger.schematron.pure.bound.xpath.PSXPathBoundElement;
43 import com.helger.schematron.pure.errorhandler.IPSErrorHandler;
44 import com.helger.schematron.pure.model.IPSElement;
45 import com.helger.schematron.pure.model.PSAssertReport;
46 import com.helger.schematron.pure.model.PSDiagnostics;
47 import com.helger.schematron.pure.model.PSDir;
48 import com.helger.schematron.pure.model.PSEmph;
49 import com.helger.schematron.pure.model.PSName;
50 import com.helger.schematron.pure.model.PSPattern;
51 import com.helger.schematron.pure.model.PSPhase;
52 import com.helger.schematron.pure.model.PSRule;
53 import com.helger.schematron.pure.model.PSSchema;
54 import com.helger.schematron.pure.model.PSSpan;
55 import com.helger.schematron.pure.model.PSTitle;
56 import com.helger.schematron.pure.model.PSValueOf;
57 import com.helger.schematron.pure.validation.IPSValidationHandler;
58 import com.helger.schematron.pure.validation.SchematronValidationException;
59 import com.helger.schematron.xpath.XPathEvaluationHelper;
60 import com.helger.xml.XMLHelper;
61
62
63
64
65
66
67
68
69 @NotThreadSafe
70 public class PSXPathValidationHandlerSVRL implements IPSValidationHandler
71 {
72 private final IPSErrorHandler m_aErrorHandler;
73 private SchematronOutputType m_aSchematronOutput;
74 private PSSchema m_aSchema;
75 private String m_sBaseURI;
76
77
78
79
80
81
82
83 public PSXPathValidationHandlerSVRL (@Nonnull final IPSErrorHandler aErrorHandler)
84 {
85 ValueEnforcer.notNull (aErrorHandler, "ErrorHandler");
86 m_aErrorHandler = aErrorHandler;
87 }
88
89 private void _warn (@Nonnull final IPSElement aSourceElement, @Nonnull final String sMsg)
90 {
91 if (m_aSchema == null)
92 throw new IllegalStateException ("No schema is present!");
93
94 m_aErrorHandler.warn (m_aSchema.getResource (), aSourceElement, sMsg);
95 }
96
97 private void _error (@Nonnull final IPSElement aSourceElement,
98 @Nonnull final String sMsg,
99 @Nullable final Throwable t)
100 {
101 if (m_aSchema == null)
102 throw new IllegalStateException ("No schema is present!");
103
104 m_aErrorHandler.error (m_aSchema.getResource (), aSourceElement, sMsg, t);
105 }
106
107 @Nullable
108 private static String _getTitleAsString (@Nullable final PSTitle aTitle) throws SchematronValidationException
109 {
110 if (aTitle == null)
111 return null;
112
113 final StringBuilder aSB = new StringBuilder ();
114 for (final Object aContent : aTitle.getAllContentElements ())
115 {
116 if (aContent instanceof String)
117 aSB.append ((String) aContent);
118 else
119 if (aContent instanceof PSDir)
120 aSB.append (((PSDir) aContent).getAsText ());
121 else
122 throw new SchematronValidationException ("Unsupported title content element: " + aContent);
123 }
124 return aSB.toString ();
125 }
126
127 @Override
128 public void onStart (@Nonnull final PSSchema aSchema,
129 @Nullable final PSPhase aActivePhase,
130 @Nullable final String sBaseURI) throws SchematronValidationException
131 {
132 final SchematronOutputType aSchematronOutput = new SchematronOutputType ();
133 if (aActivePhase != null)
134 aSchematronOutput.setPhase (aActivePhase.getID ());
135 aSchematronOutput.setSchemaVersion (aSchema.getSchemaVersion ());
136 aSchematronOutput.setTitle (_getTitleAsString (aSchema.getTitle ()));
137
138
139 for (final Map.Entry <String, String> aEntry : aSchema.getAsNamespaceContext ()
140 .getPrefixToNamespaceURIMap ()
141 .entrySet ())
142 {
143 final NsPrefixInAttributeValues aNsPrefix = new NsPrefixInAttributeValues ();
144 aNsPrefix.setPrefix (aEntry.getKey ());
145 aNsPrefix.setUri (aEntry.getValue ());
146 aSchematronOutput.getNsPrefixInAttributeValues ().add (aNsPrefix);
147 }
148 m_aSchematronOutput = aSchematronOutput;
149 m_aSchema = aSchema;
150 m_sBaseURI = sBaseURI;
151 }
152
153 @Override
154 public void onPattern (@Nonnull final PSPattern aPattern)
155 {
156 final ActivePattern aRetPattern = new ActivePattern ();
157
158 aRetPattern.setId (aPattern.getID ());
159
160
161 m_aSchematronOutput.getActivePatternAndFiredRuleAndFailedAssert ().add (aRetPattern);
162 }
163
164 @Override
165 public void onRule (@Nonnull final PSRule aRule, @Nonnull final String sContext)
166 {
167 final FiredRule aRetRule = new FiredRule ();
168 aRetRule.setContext (sContext);
169 aRetRule.setFlag (aRule.getFlag ());
170 aRetRule.setId (aRule.getID ());
171 if (aRule.hasLinkable ())
172 aRetRule.setRole (aRule.getLinkable ().getRole ());
173 m_aSchematronOutput.getActivePatternAndFiredRuleAndFailedAssert ().add (aRetRule);
174 }
175
176
177
178
179
180
181
182
183
184
185
186
187 @Nonnull
188 private String _getErrorText (@Nonnull final List <PSXPathBoundElement> aBoundContentElements,
189 @Nonnull final Node aSourceNode) throws SchematronValidationException
190 {
191 final StringBuilder aSB = new StringBuilder ();
192
193 for (final PSXPathBoundElement aBoundElement : aBoundContentElements)
194 {
195 final Object aContent = aBoundElement.getElement ();
196 if (aContent instanceof String)
197 aSB.append ((String) aContent);
198 else
199 if (aContent instanceof PSName)
200 {
201 final PSName"../../../../../../com/helger/schematron/pure/model/PSName.html#PSName">PSName aName = (PSName) aContent;
202 if (aName.hasPath ())
203 {
204
205 try
206 {
207 aSB.append ((String) XPathEvaluationHelper.evaluate (aBoundElement.getBoundExpression (),
208 aSourceNode,
209 XPathConstants.STRING,
210 m_sBaseURI));
211 }
212 catch (final XPathExpressionException ex)
213 {
214 _error (aName,
215 "Failed to evaluate XPath expression to a string: '" + aBoundElement.getExpression () + "'",
216 ex.getCause () != null ? ex.getCause () : ex);
217
218 aSB.append (aName.getPath ());
219 }
220 }
221 else
222 {
223
224 aSB.append (aSourceNode.getNodeName ());
225 }
226 }
227 else
228 if (aContent instanceof PSValueOf)
229 {
230 final PSValueOf/../../../../com/helger/schematron/pure/model/PSValueOf.html#PSValueOf">PSValueOf aValueOf = (PSValueOf) aContent;
231 try
232 {
233 aSB.append ((String) XPathEvaluationHelper.evaluate (aBoundElement.getBoundExpression (),
234 aSourceNode,
235 XPathConstants.STRING,
236 m_sBaseURI));
237 }
238 catch (final XPathExpressionException ex)
239 {
240 _error (aValueOf,
241 "Failed to evaluate XPath expression to a string: '" + aBoundElement.getExpression () + "'",
242 ex);
243
244 aSB.append (aValueOf.getSelect ());
245 }
246 }
247 else
248 if (aContent instanceof PSEmph)
249 aSB.append (((PSEmph) aContent).getAsText ());
250 else
251 if (aContent instanceof PSDir)
252 aSB.append (((PSDir) aContent).getAsText ());
253 else
254 if (aContent instanceof PSSpan)
255 aSB.append (((PSSpan) aContent).getAsText ());
256 else
257 throw new SchematronValidationException ("Unsupported assert/report content element: " + aContent);
258 }
259 return aSB.toString ();
260 }
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279 private void _handleDiagnosticReferences (@Nullable final List <String> aSrcDiagnostics,
280 @Nonnull final List <DiagnosticReference> aDstList,
281 @Nonnull final PSXPathBoundAssertReport aBoundAssertReport,
282 @Nonnull final Node aRuleMatchingNode) throws SchematronValidationException
283 {
284 if (CollectionHelper.isNotEmpty (aSrcDiagnostics))
285 {
286 if (m_aSchema.hasDiagnostics ())
287 {
288 final PSDiagnostics aDiagnostics = m_aSchema.getDiagnostics ();
289 for (final String sDiagnosticID : aSrcDiagnostics)
290 {
291 final PSXPathBoundDiagnostic aDiagnostic = aBoundAssertReport.getBoundDiagnosticOfID (sDiagnosticID);
292 if (aDiagnostic == null)
293 _warn (aDiagnostics, "Failed to resolve diagnostics with ID '" + sDiagnosticID + "'");
294 else
295 {
296
297 final DiagnosticReference aDR = new DiagnosticReference ();
298 aDR.setDiagnostic (sDiagnosticID);
299 aDR.setText (_getErrorText (aDiagnostic.getAllBoundContentElements (), aRuleMatchingNode));
300 aDstList.add (aDR);
301 }
302 }
303 }
304 else
305 _warn (m_aSchema, "Failed to resolve diagnostic because schema has no diagnostics");
306 }
307 }
308
309 @Nonnull
310 private static String _getPathToNode (@Nonnull final Node aNode)
311 {
312 return XMLHelper.getPathToNode2 (aNode, "/");
313 }
314
315 @Override
316 @Nonnull
317 public EContinue onFailedAssert (@Nonnull final PSAssertReport aAssertReport,
318 @Nonnull final String sTestExpression,
319 @Nonnull final Node aRuleMatchingNode,
320 final int nNodeIndex,
321 @Nullable final Object aContext) throws SchematronValidationException
322 {
323 if (!(aContext instanceof PSXPathBoundAssertReport))
324 throw new SchematronValidationException ("The passed context must be an XPath object but is a " + aContext);
325 final PSXPathBoundAssertReportchematron/pure/bound/xpath/PSXPathBoundAssertReport.html#PSXPathBoundAssertReport">PSXPathBoundAssertReport aBoundAssertReport = (PSXPathBoundAssertReport) aContext;
326
327 final FailedAssert aFailedAssert = new FailedAssert ();
328 aFailedAssert.setFlag (aAssertReport.getFlag ());
329 aFailedAssert.setId (aAssertReport.getID ());
330 aFailedAssert.setLocation (_getPathToNode (aRuleMatchingNode));
331 if (aAssertReport.hasLinkable ())
332 aFailedAssert.setRole (aAssertReport.getLinkable ().getRole ());
333 aFailedAssert.setTest (sTestExpression);
334 aFailedAssert.setText (_getErrorText (aBoundAssertReport.getAllBoundContentElements (), aRuleMatchingNode));
335 _handleDiagnosticReferences (aAssertReport.getAllDiagnostics (),
336 aFailedAssert.getDiagnosticReference (),
337 aBoundAssertReport,
338 aRuleMatchingNode);
339 m_aSchematronOutput.getActivePatternAndFiredRuleAndFailedAssert ().add (aFailedAssert);
340 return EContinue.CONTINUE;
341 }
342
343 @Override
344 @Nonnull
345 public EContinue onSuccessfulReport (@Nonnull final PSAssertReport aAssertReport,
346 @Nonnull final String sTestExpression,
347 @Nonnull final Node aRuleMatchingNode,
348 final int nNodeIndex,
349 @Nullable final Object aContext) throws SchematronValidationException
350 {
351 if (!(aContext instanceof PSXPathBoundAssertReport))
352 throw new SchematronValidationException ("The passed context must be an XPath object but is a " + aContext);
353 final PSXPathBoundAssertReportchematron/pure/bound/xpath/PSXPathBoundAssertReport.html#PSXPathBoundAssertReport">PSXPathBoundAssertReport aBoundAssertReport = (PSXPathBoundAssertReport) aContext;
354
355 final SuccessfulReport aSuccessfulReport = new SuccessfulReport ();
356 aSuccessfulReport.setFlag (aAssertReport.getFlag ());
357 aSuccessfulReport.setId (aAssertReport.getID ());
358 aSuccessfulReport.setLocation (_getPathToNode (aRuleMatchingNode));
359 if (aAssertReport.hasLinkable ())
360 aSuccessfulReport.setRole (aAssertReport.getLinkable ().getRole ());
361 aSuccessfulReport.setTest (sTestExpression);
362 aSuccessfulReport.setText (_getErrorText (aBoundAssertReport.getAllBoundContentElements (), aRuleMatchingNode));
363 _handleDiagnosticReferences (aAssertReport.getAllDiagnostics (),
364 aSuccessfulReport.getDiagnosticReference (),
365 aBoundAssertReport,
366 aRuleMatchingNode);
367 m_aSchematronOutput.getActivePatternAndFiredRuleAndFailedAssert ().add (aSuccessfulReport);
368 return EContinue.CONTINUE;
369 }
370
371 @Nullable
372 public SchematronOutputType getSVRL ()
373 {
374 return m_aSchematronOutput;
375 }
376 }