1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.jexl3.internal;
18
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.io.Reader;
22 import java.io.StringReader;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.Set;
29
30 import org.apache.commons.jexl3.JexlCache;
31 import org.apache.commons.jexl3.JexlContext;
32 import org.apache.commons.jexl3.JexlException;
33 import org.apache.commons.jexl3.JexlFeatures;
34 import org.apache.commons.jexl3.JexlInfo;
35 import org.apache.commons.jexl3.JexlOptions;
36 import org.apache.commons.jexl3.JxltEngine;
37 import org.apache.commons.jexl3.parser.ASTJexlScript;
38 import org.apache.commons.jexl3.parser.JexlNode;
39 import org.apache.commons.jexl3.parser.StringParser;
40 import org.apache.commons.logging.Log;
41
42
43
44
45
46 public final class TemplateEngine extends JxltEngine {
47
48
49
50 static final class Block {
51
52 private final BlockType type;
53
54 private final int line;
55
56 private final String body;
57
58
59
60
61
62
63
64 Block(final BlockType theType, final int theLine, final String theBlock) {
65 type = theType;
66 line = theLine;
67 body = theBlock;
68 }
69
70
71
72
73 String getBody() {
74 return body;
75 }
76
77
78
79
80 int getLine() {
81 return line;
82 }
83
84
85
86
87 BlockType getType() {
88 return type;
89 }
90
91
92
93
94
95
96 void toString(final StringBuilder strb, final String prefix) {
97 if (BlockType.VERBATIM.equals(type)) {
98 strb.append(body);
99 } else {
100 final Iterator<CharSequence> lines = readLines(new StringReader(body));
101 while (lines.hasNext()) {
102 strb.append(prefix).append(lines.next());
103 }
104 }
105 }
106 }
107
108
109
110 enum BlockType {
111
112 VERBATIM,
113
114 DIRECTIVE
115 }
116
117
118 final class CompositeExpression extends TemplateExpression {
119
120 private final int meta;
121
122 final TemplateExpression[] exprs;
123
124
125
126
127
128
129
130 CompositeExpression(final int[] counters, final List<TemplateExpression> list, final TemplateExpression src) {
131 super(src);
132 this.exprs = list.toArray(new TemplateExpression[0]);
133 this.meta = (counters[ExpressionType.DEFERRED.getIndex()] > 0 ? 2 : 0)
134 | (counters[ExpressionType.IMMEDIATE.getIndex()] > 0 ? 1 : 0);
135 }
136
137 @Override
138 public StringBuilder asString(final StringBuilder strb) {
139 for (final TemplateExpression e : exprs) {
140 e.asString(strb);
141 }
142 return strb;
143 }
144
145 @Override
146 protected Object evaluate(final Interpreter interpreter) {
147 Object value;
148
149 final StringBuilder strb = new StringBuilder();
150 for (final TemplateExpression expr : exprs) {
151 value = expr.evaluate(interpreter);
152 if (value != null) {
153 strb.append(value);
154 }
155 }
156 return strb.toString();
157 }
158
159 @Override
160 ExpressionType getType() {
161 return ExpressionType.COMPOSITE;
162 }
163
164 @Override
165 public Set<List<String>> getVariables() {
166 final Engine.VarCollector collector = jexl.varCollector();
167 for (final TemplateExpression expr : exprs) {
168 expr.getVariables(collector);
169 }
170 return collector.collected();
171 }
172
173
174
175
176
177 @Override
178 protected void getVariables(final Engine.VarCollector collector) {
179 for (final TemplateExpression expr : exprs) {
180 expr.getVariables(collector);
181 }
182 }
183
184 @Override
185 public boolean isImmediate() {
186
187 return (meta & 2) == 0;
188 }
189
190 @Override
191 protected TemplateExpression prepare(final Interpreter interpreter) {
192
193 if (source != this) {
194 return this;
195 }
196
197 final int size = exprs.length;
198 final ExpressionBuilder builder = new ExpressionBuilder(size);
199
200 boolean eq = true;
201 for (final TemplateExpression expr : exprs) {
202 final TemplateExpression prepared = expr.prepare(interpreter);
203
204 if (prepared != null) {
205 builder.add(prepared);
206 }
207
208 eq &= expr == prepared;
209 }
210 return eq ? this : builder.build(TemplateEngine.this, this);
211 }
212 }
213
214 final class ConstantExpression extends TemplateExpression {
215
216 private final Object value;
217
218
219
220
221
222
223
224
225
226
227 ConstantExpression(final Object val, final TemplateExpression source) {
228 super(source);
229 Objects.requireNonNull(val, "val");
230 this.value = val instanceof String
231 ? StringParser.buildTemplate((String) val, false)
232 : val;
233 }
234
235 @Override
236 public StringBuilder asString(final StringBuilder strb) {
237 if (value != null) {
238 strb.append(value);
239 }
240 return strb;
241 }
242
243 @Override
244 protected Object evaluate(final Interpreter interpreter) {
245 return value;
246 }
247
248 @Override
249 ExpressionType getType() {
250 return ExpressionType.CONSTANT;
251 }
252 }
253
254 final class DeferredExpression extends JexlBasedExpression {
255
256
257
258
259
260
261 DeferredExpression(final CharSequence expr, final JexlNode node, final TemplateExpression source) {
262 super(expr, node, source);
263 }
264
265 @Override
266 ExpressionType getType() {
267 return ExpressionType.DEFERRED;
268 }
269
270 @Override
271 protected void getVariables(final Engine.VarCollector collector) {
272
273 }
274
275 @Override
276 public boolean isImmediate() {
277 return false;
278 }
279
280 @Override
281 protected TemplateExpression prepare(final Interpreter interpreter) {
282 return new ImmediateExpression(expr, node, source);
283 }
284 }
285
286
287
288
289 static final class ExpressionBuilder {
290
291 private final int[] counts;
292
293 private final List<TemplateExpression> expressions;
294
295
296
297
298
299 ExpressionBuilder(final int size) {
300 counts = new int[]{0, 0, 0};
301 expressions = new ArrayList<>(size <= 0 ? 3 : size);
302 }
303
304
305
306
307
308 void add(final TemplateExpression expr) {
309 counts[expr.getType().getIndex()] += 1;
310 expressions.add(expr);
311 }
312
313
314
315
316
317
318
319 TemplateExpression build(final TemplateEngine el, final TemplateExpression source) {
320 int sum = 0;
321 for (final int count : counts) {
322 sum += count;
323 }
324 if (expressions.size() != sum) {
325 final StringBuilder error = new StringBuilder("parsing algorithm error: ");
326 throw new IllegalStateException(toString(error).toString());
327 }
328
329 if (expressions.size() == 1) {
330 return expressions.get(0);
331 }
332 return el.new CompositeExpression(counts, expressions, source);
333 }
334
335 @Override
336 public String toString() {
337 return toString(new StringBuilder()).toString();
338 }
339
340
341
342
343
344
345 StringBuilder toString(final StringBuilder error) {
346 error.append("exprs{");
347 error.append(expressions.size());
348 error.append(", constant:");
349 error.append(counts[ExpressionType.CONSTANT.getIndex()]);
350 error.append(", immediate:");
351 error.append(counts[ExpressionType.IMMEDIATE.getIndex()]);
352 error.append(", deferred:");
353 error.append(counts[ExpressionType.DEFERRED.getIndex()]);
354 error.append("}");
355 return error;
356 }
357 }
358
359
360
361
362
363
364 enum ExpressionType {
365
366 CONSTANT(0),
367
368 IMMEDIATE(1),
369
370 DEFERRED(2),
371
372
373 NESTED(2),
374
375 COMPOSITE(-1);
376
377 private final int index;
378
379
380
381
382
383 ExpressionType(final int idx) {
384 this.index = idx;
385 }
386
387
388
389
390 int getIndex() {
391 return index;
392 }
393 }
394
395
396 final class ImmediateExpression extends JexlBasedExpression {
397
398
399
400
401
402
403 ImmediateExpression(final CharSequence expr, final JexlNode node, final TemplateExpression source) {
404 super(expr, node, source);
405 }
406
407 @Override
408 ExpressionType getType() {
409 return ExpressionType.IMMEDIATE;
410 }
411
412 @Override
413 protected TemplateExpression prepare(final Interpreter interpreter) {
414
415 final Object value = evaluate(interpreter);
416 return value != null ? new ConstantExpression(value, source) : null;
417 }
418 }
419
420
421 abstract class JexlBasedExpression extends TemplateExpression {
422
423 protected final CharSequence expr;
424
425 protected final JexlNode node;
426
427
428
429
430
431
432
433 protected JexlBasedExpression(final CharSequence theExpr, final JexlNode theNode, final TemplateExpression theSource) {
434 super(theSource);
435 this.expr = theExpr;
436 this.node = theNode;
437 }
438
439 @Override
440 public StringBuilder asString(final StringBuilder strb) {
441 strb.append(isImmediate() ? immediateChar : deferredChar);
442 strb.append("{");
443 strb.append(expr);
444 strb.append("}");
445 return strb;
446 }
447
448 @Override
449 protected Object evaluate(final Interpreter interpreter) {
450 return interpreter.interpret(node);
451 }
452
453 @Override
454 JexlInfo getInfo() {
455 return node.jexlInfo();
456 }
457
458 @Override
459 public Set<List<String>> getVariables() {
460 final Engine.VarCollector collector = jexl.varCollector();
461 getVariables(collector);
462 return collector.collected();
463 }
464
465 @Override
466 protected void getVariables(final Engine.VarCollector collector) {
467 jexl.getVariables(node instanceof ASTJexlScript? (ASTJexlScript) node : null, node, collector);
468 }
469
470 @Override
471 protected JexlOptions options(final JexlContext context) {
472 return jexl.evalOptions(node instanceof ASTJexlScript? (ASTJexlScript) node : null, context);
473 }
474 }
475
476
477
478
479
480
481 final class NestedExpression extends JexlBasedExpression {
482
483
484
485
486
487
488 NestedExpression(final CharSequence expr, final JexlNode node, final TemplateExpression source) {
489 super(expr, node, source);
490 if (this.source != this) {
491 throw new IllegalArgumentException("Nested TemplateExpression cannot have a source");
492 }
493 }
494
495 @Override
496 public StringBuilder asString(final StringBuilder strb) {
497 strb.append(expr);
498 return strb;
499 }
500
501 @Override
502 protected Object evaluate(final Interpreter interpreter) {
503 return prepare(interpreter).evaluate(interpreter);
504 }
505
506 @Override
507 ExpressionType getType() {
508 return ExpressionType.NESTED;
509 }
510
511 @Override
512 public boolean isImmediate() {
513 return false;
514 }
515
516 @Override
517 protected TemplateExpression prepare(final Interpreter interpreter) {
518 final String value = interpreter.interpret(node).toString();
519 final JexlNode dnode = jexl.jxltParse(node.jexlInfo(), noscript, value, null);
520 return new ImmediateExpression(value, dnode, this);
521 }
522 }
523
524
525 private enum ParseState {
526
527 CONST,
528
529 IMMEDIATE0,
530
531 DEFERRED0,
532
533 IMMEDIATE1,
534
535 DEFERRED1,
536
537 ESCAPE
538 }
539
540
541
542
543 abstract class TemplateExpression implements Expression {
544
545 protected final TemplateExpression source;
546
547
548
549
550
551 TemplateExpression(final TemplateExpression src) {
552 this.source = src != null ? src : this;
553 }
554
555 @Override
556 public String asString() {
557 final StringBuilder strb = new StringBuilder();
558 asString(strb);
559 return strb.toString();
560 }
561
562
563
564
565
566
567
568 protected abstract Object evaluate(Interpreter interpreter);
569
570 @Override
571 public final Object evaluate(final JexlContext context) {
572 return evaluate(context, null, null);
573 }
574
575
576
577
578
579
580
581
582
583
584 protected final Object evaluate(final JexlContext context, final Frame frame, final JexlOptions options) {
585 try {
586 final TemplateInterpreter.Arguments args = new TemplateInterpreter.Arguments(jexl).context(context)
587 .options(options != null ? options : options(context)).frame(frame);
588 final Interpreter interpreter = jexl.createTemplateInterpreter(args);
589 return evaluate(interpreter);
590 } catch (final JexlException xjexl) {
591 final JexlException xuel = createException(xjexl.getInfo(), "evaluate", this, xjexl);
592 if (jexl.isSilent()) {
593 if (logger.isWarnEnabled()) {
594 logger.warn(xuel.getMessage(), xuel.getCause());
595 }
596 return null;
597 }
598 throw xuel;
599 }
600 }
601
602
603 JexlInfo getInfo() {
604 return null;
605 }
606
607 @Override
608 public final TemplateExpression getSource() {
609 return source;
610 }
611
612
613
614
615
616 abstract ExpressionType getType();
617
618 @Override
619 public Set<List<String>> getVariables() {
620 return Collections.emptySet();
621 }
622
623
624
625
626
627 protected void getVariables(final Engine.VarCollector collector) {
628
629 }
630
631 @Override
632 public final boolean isDeferred() {
633 return !isImmediate();
634 }
635
636 @Override
637 public boolean isImmediate() {
638 return true;
639 }
640
641
642
643
644
645
646 protected JexlOptions options(final JexlContext context) {
647 return jexl.evalOptions(null, context);
648 }
649
650
651
652
653
654
655
656 protected TemplateExpression prepare(final Interpreter interpreter) {
657 return this;
658 }
659
660 @Override
661 public final TemplateExpression prepare(final JexlContext context) {
662 return prepare(context, null, null);
663 }
664
665
666
667
668
669
670
671
672
673
674 protected final TemplateExpression prepare(final JexlContext context, final Frame frame, final JexlOptions opts) {
675 try {
676 final JexlOptions interOptions = opts != null ? opts : jexl.evalOptions(context);
677 final Interpreter interpreter = jexl.createInterpreter(context, frame, interOptions);
678 return prepare(interpreter);
679 } catch (final JexlException xjexl) {
680 final JexlException xuel = createException(xjexl.getInfo(), "prepare", this, xjexl);
681 if (jexl.isSilent()) {
682 if (logger.isWarnEnabled()) {
683 logger.warn(xuel.getMessage(), xuel.getCause());
684 }
685 return null;
686 }
687 throw xuel;
688 }
689 }
690
691 @Override
692 public final String toString() {
693 final StringBuilder strb = new StringBuilder();
694 asString(strb);
695 if (source != this) {
696 strb.append(" /*= ");
697 strb.append(source);
698 strb.append(" */");
699 }
700 return strb.toString();
701 }
702 }
703
704
705
706
707
708
709
710
711
712 private static int append(final StringBuilder strb, final CharSequence expr, final int position, final char c) {
713 strb.append(c);
714 if (c != '"' && c != '\'') {
715 return position;
716 }
717
718 final int end = expr.length();
719 boolean escape= false;
720 int index = position + 1;
721 for (; index < end; ++index) {
722 final char ec = expr.charAt(index);
723 strb.append(ec);
724 if (ec == '\\') {
725 escape = !escape;
726 } else if (escape) {
727 escape = false;
728 } else if (ec == c) {
729 break;
730 }
731 }
732 return index;
733 }
734
735
736
737
738
739
740
741
742
743 static Exception createException(final JexlInfo info,
744 final String action,
745 final TemplateExpression expr,
746 final java.lang.Exception xany) {
747 final StringBuilder strb = new StringBuilder("failed to ");
748 strb.append(action);
749 if (expr != null) {
750 strb.append(" '");
751 strb.append(expr);
752 strb.append("'");
753 }
754 final Throwable cause = xany.getCause();
755 if (cause != null) {
756 final String causeMsg = cause.getMessage();
757 if (causeMsg != null) {
758 strb.append(", ");
759 strb.append(causeMsg);
760 }
761 }
762 return new Exception(info, strb.toString(), xany);
763 }
764
765
766
767
768
769
770 static Iterator<CharSequence> readLines(final Reader reader) {
771 if (!reader.markSupported()) {
772 throw new IllegalArgumentException("mark support in reader required");
773 }
774 return new Iterator<CharSequence>() {
775 private CharSequence next = doNext();
776
777 private CharSequence doNext() {
778 final StringBuilder strb = new StringBuilder(64);
779 int c;
780 boolean eol = false;
781 try {
782 while ((c = reader.read()) >= 0) {
783 if (eol) {
784 reader.reset();
785 break;
786 }
787 if (c == '\n') {
788 eol = true;
789 }
790 strb.append((char) c);
791 reader.mark(1);
792 }
793 } catch (final IOException xio) {
794 return null;
795 }
796 return strb.length() > 0 ? strb : null;
797 }
798
799 @Override
800 public boolean hasNext() {
801 return next != null;
802 }
803
804 @Override
805 public CharSequence next() {
806 final CharSequence current = next;
807 if (current != null) {
808 next = doNext();
809 }
810 return current;
811 }
812 };
813 }
814
815
816 final JexlCache<Source, Object> cache;
817
818
819 final Engine jexl;
820
821
822 final Log logger;
823
824
825 final char immediateChar;
826
827
828 final char deferredChar;
829
830
831 final boolean noscript;
832
833
834
835
836
837
838
839
840
841 public TemplateEngine(final Engine jexl,
842 final boolean noScript,
843 final int cacheSize,
844 final char immediate,
845 final char deferred) {
846 this.jexl = jexl;
847 this.logger = jexl.logger;
848 this.cache = jexl.createCache(cacheSize);
849 immediateChar = immediate;
850 deferredChar = deferred;
851 noscript = noScript;
852 }
853
854
855
856
857 @Override
858 public void clearCache() {
859 synchronized (cache) {
860 cache.clear();
861 }
862 }
863
864 @Override
865 public JxltEngine.Expression createExpression(final JexlInfo jexlInfo, final String expression) {
866 return createExpression(jexlInfo, expression, null);
867 }
868
869 public JxltEngine.Expression createExpression(final JexlInfo jexlInfo, final String expression, final Scope scope) {
870 final JexlInfo info = jexlInfo == null ? jexl.createInfo() : jexlInfo;
871 Exception xuel = null;
872 TemplateExpression stmt = null;
873 final JexlFeatures features = noscript ? jexl.expressionFeatures : jexl.scriptFeatures;
874
875 final boolean cached = cache != null && expression.length() < jexl.cacheThreshold;
876 try {
877 if (!cached) {
878 stmt = parseExpression(info, expression, scope);
879 } else {
880 final Source source = new Source(features, Scope.getSymbolsMap(scope), expression);
881 final Object c = cache.get(source);
882 stmt = c instanceof TemplateExpression ? (TemplateExpression) c : null;
883 if (stmt != null) {
884 return stmt;
885 }
886 stmt = parseExpression(info, expression, scope);
887 cache.put(source, stmt);
888 }
889 } catch (final JexlException xjexl) {
890 xuel = new Exception(xjexl.getInfo(), "failed to parse '" + expression + "'", xjexl);
891 }
892 if (xuel != null) {
893 if (!jexl.isSilent()) {
894 throw xuel;
895 }
896 if (logger.isWarnEnabled()) {
897 logger.warn(xuel.getMessage(), xuel.getCause());
898 }
899 stmt = null;
900 }
901 return stmt;
902 }
903
904 @Override
905 public TemplateScript createTemplate(final JexlInfo info, final String prefix, final Reader source, final String... parms) {
906 return new TemplateScript(this, info, prefix, source, parms);
907 }
908
909
910
911
912 char getDeferredChar() {
913 return deferredChar;
914 }
915
916
917
918
919
920 @Override
921 public Engine getEngine() {
922 return jexl;
923 }
924
925
926
927
928 char getImmediateChar() {
929 return immediateChar;
930 }
931
932
933
934
935
936
937
938
939
940 TemplateExpression parseExpression(final JexlInfo info, final String expr, final Scope scope) {
941 final int size = expr.length();
942 final ExpressionBuilder builder = new ExpressionBuilder(0);
943 final StringBuilder strb = new StringBuilder(size);
944 ParseState state = ParseState.CONST;
945 int immediate1 = 0;
946 int deferred1 = 0;
947 int inner1 = 0;
948 boolean nested = false;
949 int inested = -1;
950 int lineno = info.getLine();
951 for (int column = 0; column < size; ++column) {
952 final char c = expr.charAt(column);
953 switch (state) {
954 case CONST:
955 if (c == immediateChar) {
956 state = ParseState.IMMEDIATE0;
957 } else if (c == deferredChar) {
958 inested = column;
959 state = ParseState.DEFERRED0;
960 } else if (c == '\\') {
961 state = ParseState.ESCAPE;
962 } else {
963
964 strb.append(c);
965 }
966 break;
967 case IMMEDIATE0:
968 if (c != '{') {
969
970 strb.append(immediateChar);
971 state = ParseState.CONST;
972
973 column -= 1;
974 continue;
975 }
976 state = ParseState.IMMEDIATE1;
977
978 if (strb.length() > 0) {
979 final TemplateExpression cexpr = new ConstantExpression(strb.toString(), null);
980 builder.add(cexpr);
981 strb.delete(0, Integer.MAX_VALUE);
982 }
983 break;
984 case DEFERRED0:
985 if (c != '{') {
986
987 strb.append(deferredChar);
988 state = ParseState.CONST;
989
990 column -= 1;
991 continue;
992 }
993 state = ParseState.DEFERRED1;
994
995 if (strb.length() > 0) {
996 final TemplateExpression cexpr = new ConstantExpression(strb.toString(), null);
997 builder.add(cexpr);
998 strb.delete(0, Integer.MAX_VALUE);
999 }
1000 break;
1001 case IMMEDIATE1:
1002 if (c == '}') {
1003 if (immediate1 > 0) {
1004 immediate1 -= 1;
1005 strb.append(c);
1006 } else {
1007
1008 final String src = escapeString(strb);
1009 final JexlInfo srcInfo = info.at(lineno, column);
1010 final TemplateExpression iexpr = new ImmediateExpression(src, jexl.jxltParse(srcInfo, noscript, src, scope), null);
1011 builder.add(iexpr);
1012 strb.delete(0, Integer.MAX_VALUE);
1013 state = ParseState.CONST;
1014 }
1015 } else {
1016 if (c == '{') {
1017 immediate1 += 1;
1018 }
1019
1020 column = append(strb, expr, column, c);
1021 }
1022 break;
1023 case DEFERRED1:
1024
1025
1026
1027 switch (c) {
1028 case '"':
1029 case '\'':
1030 strb.append(c);
1031 column = StringParser.readString(strb, expr, column + 1, c);
1032 continue;
1033 case '{':
1034 if (expr.charAt(column - 1) == immediateChar) {
1035 inner1 += 1;
1036 strb.deleteCharAt(strb.length() - 1);
1037 nested = true;
1038 } else {
1039 deferred1 += 1;
1040 strb.append(c);
1041 }
1042 continue;
1043 case '}':
1044
1045 if (deferred1 > 0) {
1046 deferred1 -= 1;
1047 strb.append(c);
1048 } else if (inner1 > 0) {
1049 inner1 -= 1;
1050 } else {
1051
1052 final String src = escapeString(strb);
1053 final JexlInfo srcInfo = info.at(lineno, column);
1054 TemplateExpression dexpr;
1055 if (nested) {
1056 dexpr = new NestedExpression(
1057 escapeString(expr.substring(inested, column + 1)),
1058 jexl.jxltParse(srcInfo, noscript, src, scope),
1059 null);
1060 } else {
1061 dexpr = new DeferredExpression(
1062 src,
1063 jexl.jxltParse(srcInfo, noscript, src, scope),
1064 null);
1065 }
1066 builder.add(dexpr);
1067 strb.delete(0, Integer.MAX_VALUE);
1068 nested = false;
1069 state = ParseState.CONST;
1070 }
1071 break;
1072 default:
1073
1074 column = append(strb, expr, column, c);
1075 break;
1076 }
1077 break;
1078 case ESCAPE:
1079 if (c == deferredChar) {
1080 strb.append(deferredChar);
1081 } else if (c == immediateChar) {
1082 strb.append(immediateChar);
1083 } else {
1084 strb.append('\\');
1085 strb.append(c);
1086 }
1087 state = ParseState.CONST;
1088 break;
1089 default:
1090 throw new UnsupportedOperationException("unexpected unified expression type");
1091 }
1092 if (c == '\n') {
1093 lineno += 1;
1094 }
1095 }
1096
1097 if (state != ParseState.CONST) {
1098
1099 switch (state) {
1100 case ESCAPE:
1101 strb.append('\\');
1102 strb.append('\\');
1103 break;
1104 case DEFERRED0:
1105 strb.append(deferredChar);
1106 break;
1107 case IMMEDIATE0:
1108 strb.append(immediateChar);
1109 break;
1110 default:
1111 throw new Exception(info.at(lineno, 0), "malformed expression: " + expr, null);
1112 }
1113 }
1114
1115 if (strb.length() > 0) {
1116 final TemplateExpression cexpr = new ConstantExpression(strb.toString(), null);
1117 builder.add(cexpr);
1118 }
1119 return builder.build(this, null);
1120 }
1121
1122 private String escapeString(final CharSequence str) {
1123 return StringParser.escapeString(str, (char) 0);
1124 }
1125
1126
1127
1128
1129
1130
1131
1132 List<Block> readTemplate(final String prefix, final Reader source) {
1133 final ArrayList<Block> blocks = new ArrayList<>();
1134 final BufferedReader reader;
1135 if (source instanceof BufferedReader) {
1136 reader = (BufferedReader) source;
1137 } else {
1138 reader = new BufferedReader(source);
1139 }
1140 final StringBuilder strb = new StringBuilder();
1141 BlockType type = null;
1142 int prefixLen;
1143 final Iterator<CharSequence> lines = readLines(reader);
1144 int lineno = 1;
1145 int start = 0;
1146 while (lines.hasNext()) {
1147 final CharSequence line = lines.next();
1148 if (line == null) {
1149 break;
1150 }
1151 if (type == null) {
1152
1153 prefixLen = startsWith(line, prefix);
1154 if (prefixLen >= 0) {
1155 type = BlockType.DIRECTIVE;
1156 strb.append(line.subSequence(prefixLen, line.length()));
1157 } else {
1158 type = BlockType.VERBATIM;
1159 strb.append(line.subSequence(0, line.length()));
1160 }
1161 start = lineno;
1162 } else if (type == BlockType.DIRECTIVE) {
1163
1164 prefixLen = startsWith(line, prefix);
1165 if (prefixLen < 0) {
1166 final Block directive = new Block(BlockType.DIRECTIVE, start, strb.toString());
1167 strb.delete(0, Integer.MAX_VALUE);
1168 blocks.add(directive);
1169 type = BlockType.VERBATIM;
1170 strb.append(line.subSequence(0, line.length()));
1171 start = lineno;
1172 } else {
1173
1174 strb.append(line.subSequence(prefixLen, line.length()));
1175 }
1176 } else {
1177
1178 prefixLen = startsWith(line, prefix);
1179 if (prefixLen >= 0) {
1180 final Block verbatim = new Block(BlockType.VERBATIM, start, strb.toString());
1181 strb.delete(0, Integer.MAX_VALUE);
1182 blocks.add(verbatim);
1183 type = BlockType.DIRECTIVE;
1184 strb.append(line.subSequence(prefixLen, line.length()));
1185 start = lineno;
1186 } else {
1187 strb.append(line.subSequence(0, line.length()));
1188 }
1189 }
1190 lineno += 1;
1191 }
1192
1193 if (type != null && strb.length() > 0) {
1194 final Block block = new Block(type, start, strb.toString());
1195 blocks.add(block);
1196 }
1197 blocks.trimToSize();
1198 return blocks;
1199 }
1200
1201
1202
1203
1204
1205
1206
1207
1208 int startsWith(final CharSequence sequence, final CharSequence pattern) {
1209 final int length = sequence.length();
1210 int s = 0;
1211 while (s < length && Character.isSpaceChar(sequence.charAt(s))) {
1212 s += 1;
1213 }
1214 if (s < length && pattern.length() <= length - s) {
1215 final CharSequence subSequence = sequence.subSequence(s, length);
1216 if (subSequence.subSequence(0, pattern.length()).equals(pattern)) {
1217 return s + pattern.length();
1218 }
1219 }
1220 return -1;
1221 }
1222 }