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