1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package com.helger.schematron.pure.preprocess;
18
19 import javax.annotation.Nonnull;
20 import javax.annotation.Nullable;
21 import javax.annotation.concurrent.NotThreadSafe;
22
23 import com.helger.commons.ValueEnforcer;
24 import com.helger.commons.collection.ext.ICommonsList;
25 import com.helger.commons.collection.ext.ICommonsMap;
26 import com.helger.commons.collection.ext.ICommonsNavigableMap;
27 import com.helger.commons.string.ToStringGenerator;
28 import com.helger.schematron.pure.binding.IPSQueryBinding;
29 import com.helger.schematron.pure.model.IPSElement;
30 import com.helger.schematron.pure.model.PSActive;
31 import com.helger.schematron.pure.model.PSAssertReport;
32 import com.helger.schematron.pure.model.PSDiagnostic;
33 import com.helger.schematron.pure.model.PSDiagnostics;
34 import com.helger.schematron.pure.model.PSDir;
35 import com.helger.schematron.pure.model.PSEmph;
36 import com.helger.schematron.pure.model.PSExtends;
37 import com.helger.schematron.pure.model.PSLet;
38 import com.helger.schematron.pure.model.PSNS;
39 import com.helger.schematron.pure.model.PSName;
40 import com.helger.schematron.pure.model.PSPattern;
41 import com.helger.schematron.pure.model.PSPhase;
42 import com.helger.schematron.pure.model.PSRule;
43 import com.helger.schematron.pure.model.PSSchema;
44 import com.helger.schematron.pure.model.PSSpan;
45 import com.helger.schematron.pure.model.PSValueOf;
46
47
48
49
50
51
52
53
54
55
56
57 @NotThreadSafe
58 public class PSPreprocessor
59 {
60 public static final boolean DEFAULT_KEEP_TITLES = false;
61 public static final boolean DEFAULT_KEEP_DIAGNOSTICS = false;
62 public static final boolean DEFAULT_KEEP_REPORTS = false;
63 public static final boolean DEFAULT_KEEP_EMPTY_PATTERNS = true;
64 public static final boolean DEFAULT_KEEP_EMPTY_SCHEMA = true;
65
66 private final IPSQueryBinding m_aQueryBinding;
67 private boolean m_bKeepTitles = DEFAULT_KEEP_TITLES;
68 private boolean m_bKeepDiagnostics = DEFAULT_KEEP_DIAGNOSTICS;
69 private boolean m_bKeepReports = DEFAULT_KEEP_REPORTS;
70 private boolean m_bKeepEmptyPatterns = DEFAULT_KEEP_EMPTY_PATTERNS;
71 private boolean m_bKeepEmptySchema = DEFAULT_KEEP_EMPTY_SCHEMA;
72
73 public PSPreprocessor (@Nonnull final IPSQueryBinding aQueryBinding)
74 {
75 m_aQueryBinding = ValueEnforcer.notNull (aQueryBinding, "QueryBinding");
76 }
77
78
79
80
81 @Nonnull
82 public IPSQueryBinding getQueryBinding ()
83 {
84 return m_aQueryBinding;
85 }
86
87
88
89
90
91 public boolean isKeepTitles ()
92 {
93 return m_bKeepTitles;
94 }
95
96 @Nonnull
97 public PSPreprocessor setKeepTitles (final boolean bKeepTitles)
98 {
99 m_bKeepTitles = bKeepTitles;
100 return this;
101 }
102
103
104
105
106
107 public boolean isKeepDiagnostics ()
108 {
109 return m_bKeepDiagnostics;
110 }
111
112 @Nonnull
113 public PSPreprocessor setKeepDiagnostics (final boolean bKeepDiagnostics)
114 {
115 m_bKeepDiagnostics = bKeepDiagnostics;
116 return this;
117 }
118
119
120
121
122
123 public boolean isKeepReports ()
124 {
125 return m_bKeepReports;
126 }
127
128 @Nonnull
129 public PSPreprocessor setKeepReports (final boolean bKeepReports)
130 {
131 m_bKeepReports = bKeepReports;
132 return this;
133 }
134
135
136
137
138
139 public boolean isKeepEmptyPatterns ()
140 {
141 return m_bKeepEmptyPatterns;
142 }
143
144 @Nonnull
145 public PSPreprocessor setKeepEmptyPatterns (final boolean bKeepEmptyPatterns)
146 {
147 m_bKeepEmptyPatterns = bKeepEmptyPatterns;
148 return this;
149 }
150
151
152
153
154
155 public boolean isKeepEmptySchema ()
156 {
157 return m_bKeepEmptySchema;
158 }
159
160
161
162
163
164
165
166
167
168
169 @Nonnull
170 public PSPreprocessor setKeepEmptySchema (final boolean bKeepEmptySchema)
171 {
172 m_bKeepEmptySchema = bKeepEmptySchema;
173 return this;
174 }
175
176 @Nonnull
177 private static PSPhase _getPreprocessedPhase (@Nonnull final PSPhase aPhase,
178 @Nonnull final PreprocessorIDPool aIDPool) throws SchematronPreprocessException
179 {
180 final PSPhase ret = new PSPhase ();
181 ret.setID (aIDPool.getUniqueID (aPhase.getID ()));
182 ret.setRich (aPhase.getRichClone ());
183 if (aPhase.hasAnyInclude ())
184 throw new SchematronPreprocessException ("Cannot preprocess <phase> with an <include>");
185 for (final IPSElement aElement : aPhase.getAllContentElements ())
186 {
187 if (aElement instanceof PSActive)
188 ret.addActive (((PSActive) aElement).getClone ());
189 else
190 if (aElement instanceof PSLet)
191 ret.addLet (((PSLet) aElement).getClone ());
192
193 }
194 ret.addForeignElements (aPhase.getAllForeignElements ());
195 ret.addForeignAttributes (aPhase.getAllForeignAttributes ());
196 return ret;
197 }
198
199
200
201
202
203
204
205
206
207
208
209
210
211 private void _resolveRuleContent (@Nonnull final ICommonsList <IPSElement> aRuleContent,
212 @Nonnull final PreprocessorLookup aLookup,
213 @Nonnull final PreprocessorIDPool aIDPool,
214 @Nullable final ICommonsMap <String, String> aParamValueMap,
215 @Nonnull final PSRule aTargetRule) throws SchematronPreprocessException
216 {
217 for (final IPSElement aElement : aRuleContent)
218 {
219 if (aElement instanceof PSAssertReport)
220 {
221 final PSAssertReport aAssertReport = (PSAssertReport) aElement;
222 aTargetRule.addAssertReport (_getPreprocessedAssert (aAssertReport, aIDPool, aParamValueMap));
223 }
224 else
225 {
226 final PSExtends aExtends = (PSExtends) aElement;
227 final String sRuleID = aExtends.getRule ();
228 final PSRule aBaseRule = aLookup.getAbstractRuleOfID (sRuleID);
229 if (aBaseRule == null)
230 throw new SchematronPreprocessException ("Failed to resolve rule ID '" +
231 sRuleID +
232 "' in extends statement. Available rules are: " +
233 aLookup.getAllAbstractRuleIDs ());
234
235
236 _resolveRuleContent (aBaseRule.getAllContentElements (), aLookup, aIDPool, aParamValueMap, aTargetRule);
237
238
239 for (final PSLet aBaseLet : aBaseRule.getAllLets ())
240 aTargetRule.addLet (aBaseLet.getClone ());
241 }
242 }
243 }
244
245 @Nonnull
246 private PSAssertReport _getPreprocessedAssert (@Nonnull final PSAssertReport aAssertReport,
247 @Nonnull final PreprocessorIDPool aIDPool,
248 @Nullable final ICommonsMap <String, String> aParamValueMap)
249 {
250 String sTest = aAssertReport.getTest ();
251 if (aAssertReport.isReport () && !m_bKeepReports)
252 {
253
254 sTest = m_aQueryBinding.getNegatedTestExpression (sTest);
255 }
256
257
258 final PSAssertReport ret = new PSAssertReport (m_bKeepReports ? aAssertReport.isAssert () : true);
259 ret.setTest (m_aQueryBinding.getWithParamTextsReplaced (sTest, aParamValueMap));
260 ret.setFlag (aAssertReport.getFlag ());
261 ret.setID (aIDPool.getUniqueID (aAssertReport.getID ()));
262 if (m_bKeepDiagnostics)
263 ret.setDiagnostics (aAssertReport.getAllDiagnostics ());
264 ret.setRich (aAssertReport.getRichClone ());
265 ret.setLinkable (aAssertReport.getLinkableClone ());
266 for (final Object aContent : aAssertReport.getAllContentElements ())
267 {
268 if (aContent instanceof String)
269 ret.addText ((String) aContent);
270 else
271 if (aContent instanceof PSName)
272 ret.addName (((PSName) aContent).getClone ());
273 else
274 if (aContent instanceof PSValueOf)
275 {
276 final PSValueOf aValueOf = ((PSValueOf) aContent).getClone ();
277 aValueOf.setSelect (m_aQueryBinding.getWithParamTextsReplaced (aValueOf.getSelect (), aParamValueMap));
278 ret.addValueOf (aValueOf);
279 }
280 else
281 if (aContent instanceof PSEmph)
282 ret.addEmph (((PSEmph) aContent).getClone ());
283 else
284 if (aContent instanceof PSDir)
285 ret.addDir (((PSDir) aContent).getClone ());
286 else
287 if (aContent instanceof PSSpan)
288 ret.addSpan (((PSSpan) aContent).getClone ());
289 }
290 ret.addForeignElements (aAssertReport.getAllForeignElements ());
291 ret.addForeignAttributes (aAssertReport.getAllForeignAttributes ());
292 return ret;
293 }
294
295 @Nullable
296 private PSRule _getPreprocessedRule (@Nonnull final PSRule aRule,
297 @Nonnull final PreprocessorLookup aLookup,
298 @Nonnull final PreprocessorIDPool aIDPool,
299 @Nullable final ICommonsMap <String, String> aParamValueMap) throws SchematronPreprocessException
300 {
301 if (aRule.isAbstract ())
302 {
303
304 return null;
305 }
306
307 final PSRule ret = new PSRule ();
308 ret.setFlag (aRule.getFlag ());
309 ret.setRich (aRule.getRichClone ());
310 ret.setLinkable (aRule.getLinkableClone ());
311
312 ret.setContext (m_aQueryBinding.getWithParamTextsReplaced (aRule.getContext (), aParamValueMap));
313 ret.setID (aIDPool.getUniqueID (aRule.getID ()));
314 if (aRule.hasAnyInclude ())
315 throw new SchematronPreprocessException ("Cannot preprocess <rule> with an <include>");
316 for (final PSLet aLet : aRule.getAllLets ())
317 ret.addLet (aLet.getClone ());
318 _resolveRuleContent (aRule.getAllContentElements (), aLookup, aIDPool, aParamValueMap, ret);
319 ret.addForeignElements (aRule.getAllForeignElements ());
320 ret.addForeignAttributes (aRule.getAllForeignAttributes ());
321 return ret;
322 }
323
324 @Nullable
325 private PSPattern _getPreprocessedPattern (@Nonnull final PSPattern aPattern,
326 @Nonnull final PreprocessorLookup aLookup,
327 @Nonnull final PreprocessorIDPool aIDPool) throws SchematronPreprocessException
328 {
329 if (aPattern.isAbstract ())
330 {
331
332 return null;
333 }
334
335 final PSPattern ret = new PSPattern ();
336
337
338 ret.setID (aIDPool.getUniqueID (aPattern.getID ()));
339 ret.setRich (aPattern.getRichClone ());
340 if (aPattern.hasAnyInclude ())
341 throw new SchematronPreprocessException ("Cannot preprocess <pattern> with an <include>");
342 if (m_bKeepTitles && aPattern.hasTitle ())
343 ret.setTitle (aPattern.getTitle ().getClone ());
344
345 final String sIsA = aPattern.getIsA ();
346 if (sIsA != null)
347 {
348 final PSPattern aBasePattern = aLookup.getAbstractPatternOfID (sIsA);
349 if (aBasePattern == null)
350 throw new SchematronPreprocessException ("Failed to resolve the pattern denoted by is-a='" + sIsA + "'");
351
352 if (!ret.hasID ())
353 ret.setID (aIDPool.getUniqueID (aBasePattern.getID ()));
354 if (!ret.hasRich ())
355 ret.setRich (aBasePattern.getRichClone ());
356
357
358 final ICommonsNavigableMap <String, String> aParamValueMap = m_aQueryBinding.getStringReplacementMap (aPattern.getAllParams ());
359
360 for (final IPSElement aElement : aBasePattern.getAllContentElements ())
361 {
362 if (aElement instanceof PSLet)
363 ret.addLet (((PSLet) aElement).getClone ());
364 else
365 if (aElement instanceof PSRule)
366 {
367 final PSRule aMinifiedRule = _getPreprocessedRule ((PSRule) aElement, aLookup, aIDPool, aParamValueMap);
368 if (aMinifiedRule != null)
369 ret.addRule (aMinifiedRule);
370 }
371
372
373 }
374 }
375 else
376 {
377 for (final IPSElement aElement : aPattern.getAllContentElements ())
378 {
379 if (aElement instanceof PSLet)
380 ret.addLet (((PSLet) aElement).getClone ());
381 else
382 if (aElement instanceof PSRule)
383 {
384 final PSRule aMinifiedRule = _getPreprocessedRule ((PSRule) aElement, aLookup, aIDPool, null);
385 if (aMinifiedRule != null)
386 ret.addRule (aMinifiedRule);
387 }
388
389
390 }
391 }
392 ret.addForeignElements (aPattern.getAllForeignElements ());
393 ret.addForeignAttributes (aPattern.getAllForeignAttributes ());
394 return ret;
395 }
396
397 @Nonnull
398 private static PSDiagnostics _getPreprocessedDiagnostics (@Nonnull final PSDiagnostics aDiagnostics) throws SchematronPreprocessException
399 {
400 final PSDiagnostics ret = new PSDiagnostics ();
401 if (aDiagnostics.hasAnyInclude ())
402 throw new SchematronPreprocessException ("Cannot preprocess <diagnostics> with an <include>");
403 for (final PSDiagnostic aDiagnostic : aDiagnostics.getAllDiagnostics ())
404 ret.addDiagnostic (aDiagnostic.getClone ());
405 ret.addForeignElements (aDiagnostics.getAllForeignElements ());
406 ret.addForeignAttributes (aDiagnostics.getAllForeignAttributes ());
407 return ret;
408 }
409
410
411
412
413
414
415
416
417
418
419
420
421
422 @Nullable
423 public PSSchema getAsMinimalSchema (@Nonnull final PSSchema aSchema) throws SchematronPreprocessException
424 {
425 ValueEnforcer.notNull (aSchema, "Schema");
426
427
428 if (aSchema.isMinimal ())
429 return aSchema;
430
431 return getForcedPreprocessedSchema (aSchema);
432 }
433
434
435
436
437
438
439
440
441
442
443
444
445
446 @Nullable
447 public PSSchema getAsPreprocessedSchema (@Nonnull final PSSchema aSchema) throws SchematronPreprocessException
448 {
449 ValueEnforcer.notNull (aSchema, "Schema");
450
451
452 if (aSchema.isPreprocessed ())
453 return aSchema;
454
455 return getForcedPreprocessedSchema (aSchema);
456 }
457
458
459
460
461
462
463
464
465
466
467
468
469
470 @Nullable
471 public PSSchema getForcedPreprocessedSchema (@Nonnull final PSSchema aSchema) throws SchematronPreprocessException
472 {
473 ValueEnforcer.notNull (aSchema, "Schema");
474
475 final PreprocessorLookup aLookup = new PreprocessorLookup (aSchema);
476 final PreprocessorIDPool aIDPool = new PreprocessorIDPool ();
477
478 final PSSchema ret = new PSSchema (aSchema.getResource ());
479 ret.setID (aIDPool.getUniqueID (aSchema.getID ()));
480 ret.setRich (aSchema.getRichClone ());
481 ret.setSchemaVersion (aSchema.getSchemaVersion ());
482 ret.setDefaultPhase (aSchema.getDefaultPhase ());
483 ret.setQueryBinding (aSchema.getQueryBinding ());
484 if (m_bKeepTitles && aSchema.hasTitle ())
485 ret.setTitle (aSchema.getTitle ().getClone ());
486 if (aSchema.hasAnyInclude ())
487 throw new SchematronPreprocessException ("Cannot preprocess <schema> with an <include>");
488 for (final PSNS aNS : aSchema.getAllNSs ())
489 ret.addNS (aNS.getClone ());
490
491 for (final PSLet aLet : aSchema.getAllLets ())
492 ret.addLet (aLet.getClone ());
493 for (final PSPhase aPhase : aSchema.getAllPhases ())
494 ret.addPhase (_getPreprocessedPhase (aPhase, aIDPool));
495 for (final PSPattern aPattern : aSchema.getAllPatterns ())
496 {
497 final PSPattern aMinifiedPattern = _getPreprocessedPattern (aPattern, aLookup, aIDPool);
498 if (aMinifiedPattern != null)
499 {
500
501 if (aMinifiedPattern.getRuleCount () > 0 || m_bKeepEmptyPatterns)
502 ret.addPattern (aMinifiedPattern);
503 }
504 }
505
506
507 if (aSchema.getPatternCount () == 0 && !m_bKeepEmptySchema)
508 return null;
509
510
511 if (m_bKeepDiagnostics && aSchema.hasDiagnostics ())
512 ret.setDiagnostics (_getPreprocessedDiagnostics (aSchema.getDiagnostics ()));
513 ret.addForeignElements (aSchema.getAllForeignElements ());
514 ret.addForeignAttributes (aSchema.getAllForeignAttributes ());
515 return ret;
516 }
517
518 @Override
519 public String toString ()
520 {
521 return new ToStringGenerator (this).append ("queryBinding", m_aQueryBinding)
522 .append ("keepTitles", m_bKeepTitles)
523 .append ("keepDiagnostics", m_bKeepDiagnostics)
524 .append ("keepReports", m_bKeepReports)
525 .append ("keepEmptyPatterns", m_bKeepEmptyPatterns)
526 .append ("keepEmptySchema", m_bKeepEmptySchema)
527 .getToString ();
528 }
529
530 @Nonnull
531 public static PSPreprocessor createPreprocessorWithoutInformationLoss (@Nonnull final IPSQueryBinding aQueryBinding)
532 {
533 final PSPreprocessor aPreprocessor = new PSPreprocessor (aQueryBinding);
534
535
536
537 aPreprocessor.setKeepReports (true);
538 aPreprocessor.setKeepDiagnostics (true);
539 aPreprocessor.setKeepTitles (true);
540
541 return aPreprocessor;
542 }
543 }