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