1
2
3
4 package de.powerstat.phplib.templateengine;
5
6
7 import java.io.BufferedReader;
8 import java.io.File;
9 import java.io.FileNotFoundException;
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.io.InputStreamReader;
13 import java.nio.charset.StandardCharsets;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.Objects;
17 import java.util.regex.Pattern;
18
19 import de.powerstat.phplib.templateengine.intern.BlockManager;
20 import de.powerstat.phplib.templateengine.intern.FileManager;
21 import de.powerstat.phplib.templateengine.intern.VariableManager;
22 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
23
24
25
26
27
28
29
30
31
32
33
34
35 public final class TemplateEngine
36 {
37
38
39
40
41
42
43
44
45 private static final String TEMPLATE = "template";
46
47
48
49
50 private static final String TEMPLATE_IS_EMPTY = "template is empty";
51
52
53
54
55 private static final int MAX_VARNAME_SIZE = 64;
56
57
58
59
60 private static final String VARNAME = "varname";
61
62
63
64
65 private static final String VARNAME_IS_EMPTY = "varname is empty";
66
67
68
69
70 private static final String VARNAME_IS_TO_LONG = "varname is to long";
71
72
73
74
75 private static final String VARNAME_DOES_NOT_MATCH_NAME_PATTERN = "varname does not match name pattern";
76
77
78
79
80 private static final Pattern VARNAME_REGEXP = Pattern.compile("^\\w{1,64}$", Pattern.UNICODE_CHARACTER_CLASS);
81
82
83
84
85 private static final Pattern BLOCK_MATCHER_REGEXP = Pattern.compile("\\{([^ \\t\\r\\n}]+)\\}");
86
87
88
89
90 private static final int MAX_TEMPLATE_SIZE = 1048576;
91
92
93
94
95
96
97
98
99 private final HandleUndefined unknowns;
100
101
102
103
104 private final VariableManager variableManager;
105
106
107
108
109 private final FileManager fileManager;
110
111
112
113
114 private final BlockManager blockManager;
115
116
117
118
119
120
121
122
123 public TemplateEngine(final TemplateEngine engine)
124 {
125 Objects.requireNonNull(engine, "engine");
126 this.unknowns = engine.unknowns;
127 this.variableManager = new VariableManager(engine.variableManager);
128 this.fileManager = new FileManager(this.variableManager, engine.fileManager);
129 this.blockManager = new BlockManager(this.variableManager, engine.blockManager);
130 }
131
132
133
134
135
136
137
138
139 public TemplateEngine(final HandleUndefined unknowns)
140 {
141 this.unknowns = unknowns;
142 this.variableManager = new VariableManager();
143 this.fileManager = new FileManager(this.variableManager);
144 this.blockManager = new BlockManager(this.variableManager);
145 }
146
147
148
149
150
151 public TemplateEngine()
152 {
153 this(HandleUndefined.REMOVE);
154 }
155
156
157
158
159
160
161
162
163
164 public static TemplateEngine newInstance(final TemplateEngine engine)
165 {
166 return new TemplateEngine(engine);
167 }
168
169
170
171
172
173
174
175
176
177
178
179
180 @SuppressWarnings("java:S1162")
181 public static TemplateEngine newInstance(final File file) throws IOException
182 {
183 Objects.requireNonNull(file, "file");
184 if (!file.isFile())
185 {
186 if (!file.exists())
187 {
188 throw new FileNotFoundException(file.getAbsolutePath());
189 }
190
191 throw new AssertionError(file.getAbsolutePath() + " is a directory and not a file!");
192 }
193 final long fileLen = file.length();
194 if (fileLen > TemplateEngine.MAX_TEMPLATE_SIZE)
195 {
196 throw new IOException("file to large: " + fileLen);
197 }
198 final var templ = new TemplateEngine();
199
200
201
202
203
204
205
206
207
208
209 templ.setFile(TemplateEngine.TEMPLATE, file);
210 return templ;
211 }
212
213
214
215
216
217
218
219
220
221
222
223
224 @SuppressFBWarnings("EXS_EXCEPTION_SOFTENING_NO_CONSTRAINTS")
225 public static TemplateEngine newInstance(final InputStream stream) throws IOException
226 {
227 Objects.requireNonNull(stream, "stream");
228 final var fileBuffer = new StringBuilder();
229 try (var reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)))
230 {
231 String line = reader.readLine();
232 while (line != null)
233 {
234 fileBuffer.append(line);
235 fileBuffer.append('\n');
236 line = reader.readLine();
237 }
238 }
239 if (fileBuffer.length() == 0)
240 {
241 throw new IllegalStateException("Empty stream");
242 }
243 final var templ = new TemplateEngine();
244 templ.setVar(TemplateEngine.TEMPLATE, fileBuffer.toString());
245 return templ;
246 }
247
248
249
250
251
252
253
254
255
256
257 public static TemplateEngine newInstance(final String template)
258 {
259 Objects.requireNonNull(template, TemplateEngine.TEMPLATE);
260 if (template.isEmpty())
261 {
262 throw new IllegalArgumentException(TemplateEngine.TEMPLATE_IS_EMPTY);
263 }
264 if (template.length() > TemplateEngine.MAX_TEMPLATE_SIZE)
265 {
266 throw new IllegalArgumentException("template to large");
267 }
268
269 final var templ = new TemplateEngine();
270 templ.setVar(TemplateEngine.TEMPLATE, template);
271 return templ;
272 }
273
274
275
276
277
278
279
280
281
282
283
284 @SuppressWarnings({"PMD.LinguisticNaming", "java:S3457"})
285 public boolean setFile(final String newVarname, final File newFile)
286 {
287 Objects.requireNonNull(newVarname, "newVarname");
288 Objects.requireNonNull(newFile, "newFile");
289 if (newVarname.isEmpty())
290 {
291 throw new IllegalArgumentException("newVarname is empty");
292 }
293 if (newVarname.length() > TemplateEngine.MAX_VARNAME_SIZE)
294 {
295 throw new IllegalArgumentException("newVarname is to long");
296 }
297 if (!TemplateEngine.VARNAME_REGEXP.matcher(newVarname).matches())
298 {
299 throw new IllegalArgumentException("newVarname does not match name pattern");
300 }
301 return this.fileManager.addFile(newVarname, newFile);
302 }
303
304
305
306
307
308
309
310
311
312
313 public String getVar(final String varname)
314 {
315 Objects.requireNonNull(varname, TemplateEngine.VARNAME);
316 if (varname.isEmpty())
317 {
318 throw new IllegalArgumentException(TemplateEngine.VARNAME_IS_EMPTY);
319 }
320 if (varname.length() > TemplateEngine.MAX_VARNAME_SIZE)
321 {
322 throw new IllegalArgumentException(TemplateEngine.VARNAME_IS_TO_LONG);
323 }
324 if (!TemplateEngine.VARNAME_REGEXP.matcher(varname).matches())
325 {
326 throw new IllegalArgumentException(TemplateEngine.VARNAME_DOES_NOT_MATCH_NAME_PATTERN);
327 }
328 return this.variableManager.getVar(varname);
329 }
330
331
332
333
334
335
336
337
338
339
340 public void setVar(final String varname, final String value)
341 {
342 Objects.requireNonNull(varname, TemplateEngine.VARNAME);
343 if (varname.isEmpty())
344 {
345 throw new IllegalArgumentException(TemplateEngine.VARNAME_IS_EMPTY);
346 }
347 if (varname.length() > TemplateEngine.MAX_VARNAME_SIZE)
348 {
349 throw new IllegalArgumentException(TemplateEngine.VARNAME_IS_TO_LONG);
350 }
351 if ((value != null) && (value.length() > TemplateEngine.MAX_TEMPLATE_SIZE))
352 {
353 throw new IllegalArgumentException("value is to large");
354 }
355 if (!TemplateEngine.VARNAME_REGEXP.matcher(varname).matches())
356 {
357 throw new IllegalArgumentException(TemplateEngine.VARNAME_DOES_NOT_MATCH_NAME_PATTERN);
358 }
359 this.variableManager.setVar(varname, value);
360 }
361
362
363
364
365
366
367
368
369
370 public void setVar(final String varname)
371 {
372 setVar(varname, "");
373 }
374
375
376
377
378
379
380
381
382
383 public void unsetVar(final String varname)
384 {
385 Objects.requireNonNull(varname, TemplateEngine.VARNAME);
386 if (varname.isEmpty())
387 {
388 throw new IllegalArgumentException(TemplateEngine.VARNAME_IS_EMPTY);
389 }
390 if (varname.length() > TemplateEngine.MAX_VARNAME_SIZE)
391 {
392 throw new IllegalArgumentException(TemplateEngine.VARNAME_IS_TO_LONG);
393 }
394 if (!TemplateEngine.VARNAME_REGEXP.matcher(varname).matches())
395 {
396 throw new IllegalArgumentException(TemplateEngine.VARNAME_DOES_NOT_MATCH_NAME_PATTERN);
397 }
398 this.variableManager.unsetVar(varname);
399 }
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416 @SuppressWarnings("PMD.LinguisticNaming")
417 public boolean setBlock(final String parent, final String varname, final String name) throws IOException
418 {
419 Objects.requireNonNull(parent, "parent");
420 Objects.requireNonNull(varname, TemplateEngine.VARNAME);
421 Objects.requireNonNull(name, "name");
422 if (parent.isEmpty() || varname.isEmpty())
423 {
424 throw new IllegalArgumentException("parent or varname is empty");
425 }
426 if ((parent.length() > TemplateEngine.MAX_VARNAME_SIZE) || (varname.length() > TemplateEngine.MAX_VARNAME_SIZE) || (name.length() > TemplateEngine.MAX_VARNAME_SIZE))
427 {
428 throw new IllegalArgumentException("parent, varname or name is to long");
429 }
430 if (!TemplateEngine.VARNAME_REGEXP.matcher(parent).matches() || !TemplateEngine.VARNAME_REGEXP.matcher(varname).matches() || (!name.isEmpty() && (!TemplateEngine.VARNAME_REGEXP.matcher(name).matches())))
431 {
432 throw new IllegalArgumentException("parent, varname or name does not match name pattern");
433 }
434 if (!this.fileManager.loadFile(parent))
435 {
436 return false;
437 }
438 return this.blockManager.setBlock(parent, varname, name);
439 }
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455 @SuppressWarnings("PMD.LinguisticNaming")
456 public boolean setBlock(final String parent, final String varname) throws IOException
457 {
458 return setBlock(parent, varname, "");
459 }
460
461
462
463
464
465
466
467
468
469
470
471 public String subst(final String varname) throws IOException
472 {
473 Objects.requireNonNull(varname, TemplateEngine.VARNAME);
474 if (varname.isEmpty())
475 {
476 throw new IllegalArgumentException(TemplateEngine.VARNAME_IS_EMPTY);
477 }
478 if (varname.length() > TemplateEngine.MAX_VARNAME_SIZE)
479 {
480 throw new IllegalArgumentException(TemplateEngine.VARNAME_IS_TO_LONG);
481 }
482 if (!TemplateEngine.VARNAME_REGEXP.matcher(varname).matches())
483 {
484 throw new IllegalArgumentException(TemplateEngine.VARNAME_DOES_NOT_MATCH_NAME_PATTERN);
485 }
486 if (!this.fileManager.loadFile(varname))
487 {
488 return "";
489 }
490 return this.variableManager.subst(varname);
491 }
492
493
494
495
496
497
498
499
500
501
502
503
504
505 @SuppressWarnings("java:S2301")
506 public String parse(final String target, final String varname, final boolean append) throws IOException
507 {
508 Objects.requireNonNull(target, "target");
509 Objects.requireNonNull(varname, TemplateEngine.VARNAME);
510 if (target.isEmpty() || varname.isEmpty())
511 {
512 throw new IllegalArgumentException("target or varname is empty");
513 }
514 if ((target.length() > TemplateEngine.MAX_VARNAME_SIZE) || (varname.length() > TemplateEngine.MAX_VARNAME_SIZE))
515 {
516 throw new IllegalArgumentException("target or varname is to long");
517 }
518 if (!TemplateEngine.VARNAME_REGEXP.matcher(target).matches() || !TemplateEngine.VARNAME_REGEXP.matcher(varname).matches())
519 {
520 throw new IllegalArgumentException("target or varname does not match name pattern");
521 }
522 if (!this.fileManager.loadFile(varname))
523 {
524 return "";
525 }
526 return this.variableManager.parse(target, varname, append);
527 }
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542 public String parse(final String target, final String varname) throws IOException
543 {
544 return parse(target, varname, false);
545 }
546
547
548
549
550
551
552
553 public List<String> getVars()
554 {
555 return this.variableManager.getVars();
556 }
557
558
559
560
561
562
563
564
565
566
567
568 public List<String> getUndefined(final String varname) throws IOException
569 {
570 Objects.requireNonNull(varname, TemplateEngine.VARNAME);
571 if (varname.isEmpty())
572 {
573 throw new IllegalArgumentException(TemplateEngine.VARNAME_IS_EMPTY);
574 }
575 if (varname.length() > TemplateEngine.MAX_VARNAME_SIZE)
576 {
577 throw new IllegalArgumentException(TemplateEngine.VARNAME_IS_TO_LONG);
578 }
579 if (!TemplateEngine.VARNAME_REGEXP.matcher(varname).matches())
580 {
581 throw new IllegalArgumentException(TemplateEngine.VARNAME_DOES_NOT_MATCH_NAME_PATTERN);
582 }
583 if (!this.fileManager.loadFile(varname))
584 {
585 return Collections.emptyList();
586 }
587 return this.variableManager.getUndefined(varname);
588 }
589
590
591
592
593
594
595
596
597
598
599 public String finish(final String template)
600 {
601 Objects.requireNonNull(template, TemplateEngine.TEMPLATE);
602 if (template.isEmpty())
603 {
604 throw new IllegalArgumentException(TemplateEngine.TEMPLATE_IS_EMPTY);
605 }
606 if (template.length() > TemplateEngine.MAX_TEMPLATE_SIZE)
607 {
608 throw new IllegalArgumentException("template is to large");
609 }
610
611 String result = template;
612 final var matcher = TemplateEngine.BLOCK_MATCHER_REGEXP.matcher(result);
613 switch (this.unknowns)
614 {
615 case KEEP:
616 break;
617 case REMOVE:
618 result = matcher.replaceAll("");
619 break;
620 case COMMENT:
621 result = matcher.replaceAll("<!-- Template variable '$1' undefined -->");
622 break;
623 default:
624 throw new AssertionError(this.unknowns);
625 }
626 return result;
627 }
628
629
630
631
632
633
634
635
636
637
638 public String get(final String varname)
639 {
640 return finish(getVar(varname));
641 }
642
643
644
645
646
647
648
649
650
651
652
653
654 @Override
655 public String toString()
656 {
657 return new StringBuilder().append("TemplateEngine[unknowns=").append(this.unknowns).append(", vManager=").append(this.variableManager).append(", fManager=").append(this.fileManager).append(", bManager=").append(this.blockManager).append(']').toString();
658 }
659
660
661
662
663
664
665
666
667 @Override
668 public int hashCode()
669 {
670 return Objects.hash(this.unknowns, this.variableManager, this.fileManager, this.blockManager);
671 }
672
673
674
675
676
677
678
679
680
681 @Override
682 public boolean equals(final Object obj)
683 {
684 if (this == obj)
685 {
686 return true;
687 }
688 if (!(obj instanceof TemplateEngine))
689 {
690 return false;
691 }
692 final TemplateEngine other = (TemplateEngine)obj;
693 return (this.unknowns == other.unknowns) && this.variableManager.equals(other.variableManager) && this.fileManager.equals(other.fileManager) && this.blockManager.equals(other.blockManager);
694 }
695
696 }