1 package org.daisy.pipeline.css;
2
3 import java.util.Collection;
4 import java.util.HashMap;
5 import java.util.HashSet;
6 import java.util.LinkedList;
7 import java.util.Map;
8 import java.util.Set;
9
10 import cz.vutbr.web.css.Term;
11 import cz.vutbr.web.css.TermFunction;
12 import cz.vutbr.web.css.TermIdent;
13 import cz.vutbr.web.css.TermPair;
14 import cz.vutbr.web.css.TermString;
15
16 import org.daisy.braille.css.BrailleCSSProperty.Display;
17
18
19
20
21
22
23 public abstract class CounterEvaluator<E> {
24
25
26
27
28 protected abstract Collection<? extends TermPair<String,Integer>> getCounterReset(E elementStyle);
29
30
31
32
33 protected abstract Collection<? extends TermPair<String,Integer>> getCounterSet(E elementStyle);
34
35
36
37
38 protected abstract Collection<? extends TermPair<String,Integer>> getCounterIncrement(E elementStyle);
39
40
41
42
43 protected abstract Display getDisplay(E elementStyle);
44
45
46
47
48
49
50
51 protected abstract Collection<Term<?>> getMarkerContent(E elementStyle);
52
53
54
55
56 protected abstract CounterStyle getListStyleType(E elementStyle, CounterStyle parentListStyleType);
57
58
59
60
61 protected abstract CounterStyle getNamedCounterStyle(String name);
62
63 private final LinkedList<CounterStyle> listCounterStyle = new LinkedList<>();
64 protected final LinkedList<Map<String,Integer>> counterValues = new LinkedList<>();
65 private boolean previousEventWasStartElement = true;
66
67
68
69
70
71
72
73
74
75
76
77 public void startElement(E elementStyle) {
78 if (listCounterStyle.isEmpty())
79 listCounterStyle.push(CounterStyle.DISC);
80 if (previousEventWasStartElement)
81 counterValues.push(null);
82 previousEventWasStartElement = true;
83 updateCounterValues(elementStyle);
84 updateCurrentListCounterStyle(elementStyle);
85 }
86
87
88
89
90
91 public void endElement() {
92 listCounterStyle.pop();
93 if (!previousEventWasStartElement)
94 counterValues.pop();
95 previousEventWasStartElement = false;
96 }
97
98
99
100
101 private void updateCounterValues(E elementStyle) {
102
103 {
104 Collection<? extends TermPair<String,Integer>> list = getCounterReset(elementStyle);
105 if (list != null) {
106 for (TermPair<String,Integer> p : list) {
107 String name = p.getKey();
108 Map<String,Integer> createdBySiblingOrSelf = counterValues.peek();
109 if (createdBySiblingOrSelf == null) {
110 createdBySiblingOrSelf = new HashMap<>();
111 counterValues.set(0, createdBySiblingOrSelf);
112 }
113 createdBySiblingOrSelf.put(name, p.getValue());
114 }
115 }
116 }
117
118 Set<String> done = null;
119 {
120 Collection<? extends TermPair<String,Integer>> list = getCounterSet(elementStyle);
121 if (list != null) {
122 for (TermPair<String,Integer> p : list) {
123 String name = p.getKey();
124 boolean exists = false;
125 for (Map<String,Integer> c : counterValues) {
126 if (c != null) {
127 c.put(name, p.getValue());
128 exists = true;
129 break;
130 }
131 }
132 if (!exists) {
133 Map<String,Integer> createdBySiblingOrSelf = counterValues.peek();
134 if (createdBySiblingOrSelf == null) {
135 createdBySiblingOrSelf = new HashMap<>();
136 counterValues.set(0, createdBySiblingOrSelf);
137 }
138 createdBySiblingOrSelf.put(name, p.getValue());
139 }
140 if (done == null)
141 done = new HashSet<>();
142 done.add(name);
143 }
144 }
145 }
146 {
147 Collection<? extends TermPair<String,Integer>> list = getCounterIncrement(elementStyle);
148 if (list != null) {
149 for (TermPair<String,Integer> p : list) {
150 String name = p.getKey();
151 if (done == null || !done.contains(name)) {
152 boolean exists = false;
153 for (Map<String,Integer> c : counterValues) {
154 if (c != null) {
155 c.put(name, c.getOrDefault(name, 0) + p.getValue());
156 exists = true;
157 break;
158 }
159 }
160 if (!exists) {
161 Map<String,Integer> createdBySiblingOrSelf = counterValues.peek();
162 if (createdBySiblingOrSelf == null) {
163 createdBySiblingOrSelf = new HashMap<>();
164 counterValues.set(0, createdBySiblingOrSelf);
165 }
166 createdBySiblingOrSelf.put(name, p.getValue());
167 }
168 if (done == null)
169 done = new HashSet<>();
170 done.add(name);
171 }
172 }
173 }
174 }
175
176 if ((done == null || !done.contains("list-item")) && getDisplay(elementStyle) == Display.LIST_ITEM) {
177 boolean exists = false;
178 for (Map<String,Integer> c : counterValues) {
179 if (c != null) {
180 c.put("list-item", c.getOrDefault("list-item", 0) + 1);
181 exists = true;
182 break;
183 }
184 }
185 if (!exists) {
186 Map<String,Integer> createdBySiblingOrSelf = counterValues.peek();
187 if (createdBySiblingOrSelf == null) {
188 createdBySiblingOrSelf = new HashMap<>();
189 counterValues.set(0, createdBySiblingOrSelf);
190 }
191 createdBySiblingOrSelf.put("list-item", 1);
192 }
193 }
194 }
195
196
197
198
199 private void updateCurrentListCounterStyle(E elementStyle) {
200 listCounterStyle.push(getListStyleType(elementStyle, listCounterStyle.peek()));
201 }
202
203
204
205
206 public String generateMarkerContents(E elementStyle) throws IllegalArgumentException {
207 if (getDisplay(elementStyle) == Display.LIST_ITEM) {
208 Collection<Term<?>> content = getMarkerContent(elementStyle);
209 if (content != null) {
210 if (!content.isEmpty())
211 return evaluateContent(content);
212 else
213 return null;
214 }
215 CounterStyle listCounterStyle = this.listCounterStyle.peek();
216 if (listCounterStyle != null)
217 return evaluateCounter("list-item", listCounterStyle, true);
218 }
219 return null;
220 }
221
222 private String evaluateContent(Collection<Term<?>> content) throws IllegalArgumentException {
223 StringBuilder s = new StringBuilder();
224 for (Term<?> t : content) {
225 if (t instanceof TermString) {
226 s.append(((TermString)t).getValue());
227 } else if (t instanceof TermFunction) {
228 TermFunction f = (TermFunction)t;
229 if ("counter".equalsIgnoreCase(f.getFunctionName())) {
230 String name = null;
231 CounterStyle style = null;
232 for (Term<?> arg : f)
233 if (name == null)
234 if (arg instanceof TermIdent)
235 name = ((TermIdent)arg).getValue();
236 else
237 throw new IllegalArgumentException(
238 "invalid first argument of counter() function: should be the counter name");
239 else if (style == null) {
240 if (arg instanceof TermIdent)
241 style = getNamedCounterStyle(((TermIdent)arg).getValue());
242 else if (arg instanceof TermFunction)
243 try {
244 style = CounterStyle.fromSymbolsFunction((TermFunction)arg);
245 } catch (IllegalArgumentException e) {
246 throw new IllegalArgumentException(
247 "invalid second argument of counter() function: should be a counter style", e);
248 }
249 else
250 throw new IllegalArgumentException(
251 "invalid second argument of counter() function: should be a counter style"); }
252 else
253 throw new IllegalArgumentException(
254 "unexpected argument of counter() function: function takes at most two arguments");
255 if (name == null)
256 throw new IllegalArgumentException("counter() function requires at least one argument");
257 if (style == null)
258 style = CounterStyle.DECIMAL;
259 s.append(evaluateCounter(name, style, false));
260 } else if ("counters".equalsIgnoreCase(f.getFunctionName())) {
261 String name = null;
262 String separator = null;
263 CounterStyle style = null;
264 for (Term<?> arg : f)
265 if (name == null)
266 if (arg instanceof TermIdent)
267 name = ((TermIdent)arg).getValue();
268 else
269 throw new IllegalArgumentException(
270 "invalid first argument of counters() function: should be the counter name");
271 else if (separator == null) {
272 if (arg instanceof TermString)
273 separator = ((TermString)arg).getValue();
274 else
275 throw new IllegalArgumentException(
276 "invalid second argument of counters() function: should be a string"); }
277 else if (style == null) {
278 if (arg instanceof TermIdent)
279 style = getNamedCounterStyle(((TermIdent)arg).getValue());
280 else if (arg instanceof TermFunction)
281 try {
282 style = CounterStyle.fromSymbolsFunction((TermFunction)arg);
283 } catch (IllegalArgumentException e) {
284 throw new IllegalArgumentException(
285 "invalid third argument of counters() function: should be a counter style", e);
286 }
287 else
288 throw new IllegalArgumentException(
289 "invalid third argument of counters() function: should be a counter style"); }
290 else
291 throw new IllegalArgumentException(
292 "unexpected argument of counters() function: function takes at most three arguments");
293 if (name == null || separator == null)
294 throw new IllegalArgumentException("counters() function requires at least two arguments");
295 if (style == null)
296 style = CounterStyle.DECIMAL;
297 s.append(evaluateCounters(name, style, separator));
298 } else
299 throw new RuntimeException(f.getFunctionName() + "() function not supported in content list");
300 } else
301 throw new IllegalStateException();
302 }
303 return s.toString();
304 }
305
306
307
308
309
310 public String evaluateCounter(String name, CounterStyle style, boolean withPrefixAndSuffix) {
311 int value = evaluateCounter(name);
312 return style.format(value, withPrefixAndSuffix);
313 }
314
315
316
317
318
319 public int evaluateCounter(String name) {
320 Integer value = null;
321 boolean exists = false;
322 for (Map<String,Integer> c : counterValues)
323 if (c != null && c.containsKey(name)) {
324 value = c.get(name);
325 break;
326 }
327 if (value == null) {
328 value = 0;
329 Map<String,Integer> createdBySiblingOrSelf = counterValues.peek();
330 if (createdBySiblingOrSelf == null) {
331 createdBySiblingOrSelf = new HashMap<>();
332 counterValues.set(0, createdBySiblingOrSelf);
333 }
334 createdBySiblingOrSelf.put(name, value);
335 }
336 return value;
337 }
338
339
340
341
342
343 public String evaluateCounters(String name, CounterStyle style, String separator) {
344 StringBuilder s = new StringBuilder();
345 for (Map<String,Integer> c : counterValues)
346 if (c != null && c.containsKey(name)) {
347 if (s.length() > 0)
348 s.insert(0, separator);
349 s.insert(0, style.format(c.get(name)));
350 }
351 if (s.length() == 0) {
352 Map<String,Integer> createdBySiblingOrSelf = counterValues.peek();
353 if (createdBySiblingOrSelf == null) {
354 createdBySiblingOrSelf = new HashMap<>();
355 counterValues.set(0, createdBySiblingOrSelf);
356 }
357 createdBySiblingOrSelf.put(name, 0);
358 s.append(style.format(0));
359 }
360 return s.toString();
361 }
362 }