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