]> WPIA git - gigi.git/blob - src/club/wpia/gigi/output/template/SprintfCommand.java
upd: document variables in SprintfCommand more clearly
[gigi.git] / src / club / wpia / gigi / output / template / SprintfCommand.java
1 package club.wpia.gigi.output.template;
2
3 import java.io.PrintWriter;
4 import java.util.Arrays;
5 import java.util.Collection;
6 import java.util.HashMap;
7 import java.util.LinkedList;
8 import java.util.List;
9 import java.util.Map;
10 import java.util.regex.Matcher;
11 import java.util.regex.Pattern;
12
13 import club.wpia.gigi.Gigi;
14 import club.wpia.gigi.localisation.Language;
15 import club.wpia.gigi.util.HTMLEncoder;
16
17 /**
18  * A pattern that is to be translated before variables are inserted.
19  */
20 public final class SprintfCommand implements Translatable {
21
22     /**
23      * The pattern to fill. Containing placeholders of pattern
24      * {@link #placeholder}. This is the string that will be translated.
25      */
26     private final String pattern;
27
28     /**
29      * A regex that matches the replacement patterns like "{0}" or "{1}".
30      */
31     private static final Pattern placeholder = Pattern.compile("\\{([0-9]+)\\}");
32
33     /**
34      * The values describing what to put into the {@link #placeholder}s of
35      * {@link #pattern}.
36      */
37     private final String[] replacements;
38
39     /**
40      * Regex for detecting processing instructions in a in-template
41      * SprintfCommand.
42      */
43     private static final Pattern processingInstruction = Pattern.compile("(?:(\\$!?\\{[a-zA-Z0-9_-]+)\\})|(?:(!'[^{}'\\$]*)')|(?:(!\\([^{})\\$]*)\\))");
44
45     /**
46      * Creates a new SprintfCommand based on its pre-parsed contents. This is
47      * the variant that the data is stored internally in this class. So the
48      * <code>pattern</code> has numbers as placeholders and the replacement list
49      * contains the instructions on what to put in there (without closing
50      * brackets, etc.).
51      * 
52      * @param pattern
53      *            a string with <code>{0},{1},..</code> as placeholders.
54      * @param replacements
55      *            instructions for what data to put into the placeholders:
56      *            <code>${var</code>, <code>$!{var</code>, <code>!'plain</code>,
57      *            <code>!(/link</code>.
58      */
59     public SprintfCommand(String pattern, List<String> replacements) {
60         this.pattern = pattern;
61         this.replacements = replacements.toArray(new String[replacements.size()]);
62     }
63
64     /**
65      * Creates a new SprintfCommand that is parsed as from template source. This
66      * version is internally used to create {@link SprintfCommand}s from the
67      * literals specified in {@link Template}s.
68      * 
69      * @param content
70      *            the part from the template that is to be parsed.
71      */
72     protected SprintfCommand(String content) {
73         StringBuffer pattern = new StringBuffer();
74         List<String> replacements = new LinkedList<String>();
75         int counter = 0;
76         Matcher m = processingInstruction.matcher(content);
77         int last = 0;
78         while (m.find()) {
79             pattern.append(content.substring(last, m.start()));
80             String group = null;
81             if ((group = m.group(1)) != null) {
82                 replacements.add(group);
83             } else if ((group = m.group(2)) != null) {
84                 replacements.add(group);
85             } else if ((group = m.group(3)) != null) {
86                 replacements.add(group);
87             } else {
88                 throw new Error("Regex is broken??");
89             }
90             last = m.end();
91             pattern.append("{" + (counter++) + "}");
92         }
93         pattern.append(content.substring(last));
94         this.pattern = pattern.toString();
95         this.replacements = replacements.toArray(new String[replacements.size()]);
96     }
97
98     @Override
99     public void output(PrintWriter out, Language l, Map<String, Object> externalVariables) {
100         String parts = l.getTranslation(pattern);
101         Matcher m = placeholder.matcher(parts);
102         int pos = 0;
103         while (m.find()) {
104             out.print(escape(externalVariables, parts.substring(pos, m.start())));
105             String replacement = replacements[Integer.parseInt(m.group(1))];
106             if (replacement.startsWith("$!")) {
107                 Template.outputVar(out, l, externalVariables, replacement.substring(3), true);
108             } else if (replacement.startsWith("!'")) {
109                 out.print(replacement.substring(2));
110             } else if (replacement.startsWith("!(")) {
111                 String host = (String) externalVariables.get(Gigi.LINK_HOST);
112                 if (host == null) {
113                     throw new Error("Unconfigured link-host while interpreting link-syntax.");
114                 }
115                 if (replacement.charAt(2) != '/') {
116                     throw new Error("Need an absolute link for the link service.");
117                 }
118                 String link = "//" + host + replacement.substring(2);
119                 out.print("<a href='" + HTMLEncoder.encodeHTML(link) + "'>");
120             } else if (replacement.startsWith("$")) {
121                 Template.outputVar(out, l, externalVariables, replacement.substring(2), false);
122             } else {
123                 throw new Error("Processing error in template.");
124             }
125             pos = m.end();
126
127         }
128         out.print(escape(externalVariables, parts.substring(pos)));
129     }
130
131     private String escape(Map<String, Object> vars, String target) {
132         if (vars.containsKey(OUT_KEY_PLAIN)) {
133             return target;
134         }
135         return HTMLEncoder.encodeHTML(target);
136     }
137
138     @Override
139     public void addTranslations(Collection<String> s) {
140         s.add(pattern);
141     }
142
143     /**
144      * Creates a simple {@link SprintfCommand} wrapped in a {@link Scope} to fit
145      * in now constant variables into this template.
146      * 
147      * @param msg
148      *            the message (to be translated) with <code>{0},{1},...</code>
149      *            as placeholders.
150      * @param vars
151      *            the contents of the variables to put into the placeholders.
152      * @return the constructed {@link Outputable}.
153      */
154     public static Outputable createSimple(String msg, Object... vars) {
155         HashMap<String, Object> scope = new HashMap<>();
156         String[] store = new String[vars.length];
157         for (int i = 0; i < vars.length; i++) {
158             scope.put("autoVar" + i, vars[i]);
159             store[i] = "${autoVar" + i;
160         }
161         return new Scope(new SprintfCommand(msg, Arrays.asList(store)), scope);
162     }
163 }