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