1 package org.daisy.pipeline.braille.css.impl;
2
3 import java.io.File;
4 import java.net.URI;
5 import java.net.URISyntaxException;
6 import java.util.ArrayList;
7 import java.util.Arrays;
8 import java.util.Collection;
9 import java.util.Collections;
10 import java.util.Comparator;
11 import java.util.HashMap;
12 import java.util.HashSet;
13 import java.util.Iterator;
14 import java.util.List;
15 import java.util.Map;
16 import java.util.Optional;
17 import java.util.regex.Matcher;
18 import java.util.regex.Pattern;
19 import java.util.Set;
20
21 import javax.xml.namespace.QName;
22 import javax.xml.transform.URIResolver;
23
24 import com.google.common.base.MoreObjects;
25 import com.google.common.collect.ImmutableList;
26 import com.google.common.collect.ImmutableMap;
27 import com.google.common.collect.Iterables;
28
29 import cz.vutbr.web.css.Declaration;
30 import cz.vutbr.web.css.NodeData;
31 import cz.vutbr.web.css.Rule;
32 import cz.vutbr.web.css.RuleFactory;
33 import cz.vutbr.web.css.RuleMargin;
34 import cz.vutbr.web.css.RulePage;
35 import cz.vutbr.web.css.Selector.PseudoElement;
36 import cz.vutbr.web.css.StyleSheet;
37 import cz.vutbr.web.css.SupportedCSS;
38 import cz.vutbr.web.css.Term;
39 import cz.vutbr.web.css.TermIdent;
40 import cz.vutbr.web.css.TermURI;
41 import cz.vutbr.web.csskit.antlr.CSSParserFactory;
42 import cz.vutbr.web.csskit.DeclarationImpl;
43 import cz.vutbr.web.csskit.RuleFactoryImpl;
44 import cz.vutbr.web.csskit.TermURIImpl;
45 import cz.vutbr.web.domassign.DeclarationTransformer;
46
47 import org.daisy.braille.css.BrailleCSSExtension;
48 import org.daisy.braille.css.BrailleCSSParserFactory;
49 import org.daisy.braille.css.BrailleCSSParserFactory.Context;
50 import org.daisy.braille.css.BrailleCSSProperty;
51 import org.daisy.braille.css.BrailleCSSRuleFactory;
52 import org.daisy.braille.css.RuleCounterStyle;
53 import org.daisy.braille.css.RuleHyphenationResource;
54 import org.daisy.braille.css.RuleTextTransform;
55 import org.daisy.braille.css.RuleVolume;
56 import org.daisy.braille.css.RuleVolumeArea;
57 import org.daisy.braille.css.SelectorImpl.PseudoElementImpl;
58 import org.daisy.braille.css.SupportedBrailleCSS;
59 import org.daisy.braille.css.VendorAtRule;
60 import org.daisy.common.file.URLs;
61 import org.daisy.common.transform.XMLTransformer;
62 import org.daisy.pipeline.braille.css.SupportedPrintCSS;
63 import org.daisy.pipeline.braille.css.impl.BrailleCssParser;
64 import org.daisy.pipeline.braille.css.impl.BrailleCssSerializer;
65 import org.daisy.pipeline.css.CssCascader;
66 import org.daisy.pipeline.css.CssPreProcessor;
67 import org.daisy.pipeline.css.JStyleParserCssCascader;
68 import org.daisy.pipeline.css.Medium;
69 import org.daisy.pipeline.css.XsltProcessor;
70
71 import org.osgi.service.component.annotations.Activate;
72 import org.osgi.service.component.annotations.Component;
73 import org.osgi.service.component.annotations.Reference;
74 import org.osgi.service.component.annotations.ReferenceCardinality;
75 import org.osgi.service.component.annotations.ReferencePolicy;
76
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
79
80 import org.w3c.dom.Element;
81 import org.w3c.dom.Node;
82
83 @Component(
84 name = "BrailleCssCascader",
85 service = { CssCascader.class }
86 )
87 public class BrailleCssCascader implements CssCascader {
88
89 private static final Logger logger = LoggerFactory.getLogger(BrailleCssCascader.class);
90
91
92
93
94
95 public boolean supportsMedium(Medium medium) {
96 switch (medium.getType()) {
97 case EMBOSSED:
98 case BRAILLE:
99 case PRINT:
100 return true;
101 default:
102 return false;
103 }
104 }
105
106 public XMLTransformer newInstance(Medium medium,
107 String userAndUserAgentStylesheets,
108 URIResolver uriResolver,
109 CssPreProcessor preProcessor,
110 XsltProcessor xsltProcessor,
111 QName attributeName,
112 boolean multipleAttrs) {
113 if (multipleAttrs)
114 throw new UnsupportedOperationException("Cascading to multiple attributes per element not supported");
115 if (attributeName == null)
116 throw new UnsupportedOperationException("A style attribute must be specified");
117 switch (medium.getType()) {
118 case EMBOSSED:
119 case BRAILLE:
120 return new Transformer(uriResolver, preProcessor, xsltProcessor, userAndUserAgentStylesheets, medium, attributeName,
121 brailleParserFactory, brailleRuleFactory, brailleCSS, brailleCSS);
122 case PRINT:
123 return new Transformer(uriResolver, preProcessor, xsltProcessor, userAndUserAgentStylesheets, medium, attributeName,
124 printParserFactory, printRuleFactory, printCSS, printDeclarationTransformer);
125 default:
126 throw new IllegalArgumentException("medium not supported: " + medium);
127 }
128 }
129
130
131 private static final SupportedCSS printCSS = SupportedPrintCSS.getInstance();
132 private static DeclarationTransformer printDeclarationTransformer = new DeclarationTransformer(printCSS);
133 private static final RuleFactory printRuleFactory = RuleFactoryImpl.getInstance();
134 private static final CSSParserFactory printParserFactory = CSSParserFactory.getInstance();
135
136
137 private final List<BrailleCSSExtension> brailleCSSExtensions = new ArrayList<>();
138 private SupportedBrailleCSS brailleCSS = null;
139 private BrailleCSSRuleFactory brailleRuleFactory = null;
140 private BrailleCSSParserFactory brailleParserFactory = null;
141 private BrailleCssParser brailleCSSParser = null;
142
143
144 @Reference(
145 name = "BrailleCSSExtension",
146 unbind = "-",
147 service = BrailleCSSExtension.class,
148 cardinality = ReferenceCardinality.MULTIPLE,
149 policy = ReferencePolicy.STATIC
150 )
151 protected void addBrailleCSSExtension(BrailleCSSExtension x) {
152 logger.debug("Binding BrailleCSSExtension: {}", x);
153 brailleCSSExtensions.add(x);
154 }
155
156 @Activate
157 protected void init() {
158 boolean allowUnknownVendorExtensions = false;
159 brailleCSS = new SupportedBrailleCSS(false, true, brailleCSSExtensions, allowUnknownVendorExtensions);
160 brailleRuleFactory = new BrailleCSSRuleFactory(brailleCSSExtensions, allowUnknownVendorExtensions);
161 brailleParserFactory = new BrailleCSSParserFactory(brailleRuleFactory);
162 brailleCSSParser = new BrailleCssParser() {
163 @Override
164 public BrailleCSSParserFactory getBrailleCSSParserFactory() {
165 return brailleParserFactory;
166 }
167 @Override
168 public Optional<SupportedBrailleCSS> getSupportedBrailleCSS(Context context) {
169 switch (context) {
170 case ELEMENT:
171 case PAGE:
172 case VOLUME:
173 return Optional.of(brailleCSS);
174 default:
175 return Optional.empty();
176 }
177 }
178 };
179 }
180
181 private class Transformer extends JStyleParserCssCascader {
182
183 private final QName attributeName;
184 private final boolean isBrailleCss;
185
186 private Transformer(URIResolver resolver, CssPreProcessor preProcessor, XsltProcessor xsltProcessor,
187 String userAndUserAgentStyleSheets, Medium medium, QName attributeName,
188 CSSParserFactory parserFactory, RuleFactory ruleFactory,
189 SupportedCSS supportedCss, DeclarationTransformer declarationTransformer) {
190 super(resolver, preProcessor, xsltProcessor, userAndUserAgentStyleSheets, medium, attributeName,
191 parserFactory, ruleFactory, supportedCss, declarationTransformer);
192 this.attributeName = attributeName;
193 this.isBrailleCss = medium.getType() == Medium.Type.EMBOSSED || medium.getType() == Medium.Type.BRAILLE;
194 }
195
196 private Map<String,Map<String,RulePage>> pageRules = null;
197 private Map<String,Map<String,RuleVolume>> volumeRules = null;
198 private Iterable<RuleTextTransform> textTransformRules = null;
199 private Iterable<RuleHyphenationResource> hyphenationResourceRules = null;
200 private Iterable<RuleCounterStyle> counterStyleRules = null;
201 private Iterable<VendorAtRule<? extends Rule<?>>> otherAtRules = null;
202
203 protected Map<QName,String> serializeStyle(NodeData mainStyle, Map<PseudoElement,NodeData> pseudoStyles, Element context) {
204 if (isBrailleCss && pageRules == null) {
205 StyleSheet styleSheet = getParsedStyleSheet();
206 pageRules = new HashMap<String,Map<String,RulePage>>(); {
207 for (RulePage r : Iterables.filter(styleSheet, RulePage.class)) {
208 String name = MoreObjects.firstNonNull(r.getName(), "auto");
209 String pseudo = MoreObjects.firstNonNull(r.getPseudo(), "");
210 Map<String,RulePage> pageRule = pageRules.get(name);
211 if (pageRule == null) {
212 pageRule = new HashMap<String,RulePage>();
213 pageRules.put(name, pageRule); }
214 if (pageRule.containsKey(pseudo))
215 pageRule.put(pseudo, makePageRule(name, "".equals(pseudo) ? null : pseudo,
216 ImmutableList.of(r, pageRule.get(pseudo))));
217 else
218 pageRule.put(pseudo, r);
219 }
220 }
221 volumeRules = new HashMap<String,Map<String,RuleVolume>>(); {
222 for (RuleVolume r : Iterables.filter(styleSheet, RuleVolume.class)) {
223 String name = "auto";
224 String pseudo = MoreObjects.firstNonNull(r.getPseudo(), "");
225 if (pseudo.equals("nth(1)")) pseudo = "first";
226 else if (pseudo.equals("nth-last(1)")) pseudo = "last";
227 Map<String,RuleVolume> volumeRule = volumeRules.get(name);
228 if (volumeRule == null) {
229 volumeRule = new HashMap<String,RuleVolume>();
230 volumeRules.put(name, volumeRule); }
231 if (volumeRule.containsKey(pseudo))
232 volumeRule.put(pseudo, makeVolumeRule("".equals(pseudo) ? null : pseudo,
233 ImmutableList.of(r, volumeRule.get(pseudo))));
234 else
235 volumeRule.put(pseudo, r);
236 }
237 }
238 textTransformRules = Iterables.filter(styleSheet, RuleTextTransform.class);
239 hyphenationResourceRules = Iterables.filter(styleSheet, RuleHyphenationResource.class);
240 counterStyleRules = Iterables.filter(styleSheet, RuleCounterStyle.class);
241 otherAtRules = (Iterable<VendorAtRule<? extends Rule<?>>>)(Iterable)Iterables.filter(styleSheet, VendorAtRule.class);
242 }
243 StringBuilder style = new StringBuilder();
244 if (mainStyle != null)
245 insertStyle(style, mainStyle);
246 for (PseudoElement pseudo : sort(pseudoStyles.keySet(), pseudoElementComparator)) {
247 NodeData nd = pseudoStyles.get(pseudo);
248 if (nd != null)
249 insertPseudoStyle(style, nd, pseudo, pageRules);
250 }
251 if (isBrailleCss) {
252 boolean isRoot = (context.getParentNode().getNodeType() != Node.ELEMENT_NODE);
253 Map<String,RulePage> pageRule = getPageRule(mainStyle, pageRules);
254 if (pageRule != null) {
255 insertPageStyle(style, pageRule); }
256 else if (isRoot) {
257 pageRule = getPageRule("auto", pageRules);
258 if (pageRule != null)
259 insertPageStyle(style, pageRule); }
260 if (isRoot) {
261 Map<String,RuleVolume> volumeRule = getVolumeRule("auto", volumeRules);
262 if (volumeRule != null)
263 insertVolumeStyle(style, volumeRule, pageRules);
264 for (RuleTextTransform r : textTransformRules)
265 insertTextTransformDefinition(style, r);
266 for (RuleHyphenationResource r : hyphenationResourceRules)
267 insertHyphenationResourceDefinition(style, r);
268 for (RuleCounterStyle r : counterStyleRules)
269 insertCounterStyleDefinition(style, r);
270 for (VendorAtRule<? extends Rule<?>> r : otherAtRules) {
271 if (style.length() > 0 && !style.toString().endsWith("} ")) {
272 style.insert(0, "{ ");
273 style.append("} "); }
274 insertAtRule(style, r); }}}
275 if (style.toString().trim().isEmpty())
276 return null;
277 if (style.length() > 1 && style.substring(style.length() - 2).equals("; "))
278 style.delete(style.length() - 2, style.length());
279 return ImmutableMap.of(attributeName, style.toString().trim());
280 }
281
282 protected String serializeValue(Term<?> value) {
283 return BrailleCssSerializer.toString(value);
284 }
285 }
286
287 @SuppressWarnings("unused")
288 private static <T extends Comparable<? super T>> Iterable<T> sort(Iterable<T> iterable) {
289 List<T> list = new ArrayList<T>();
290 for (T x : iterable)
291 list.add(x);
292 Collections.<T>sort(list);
293 return list;
294 }
295
296 private static <T> Iterable<T> sort(Iterable<T> iterable, Comparator<? super T> comparator) {
297 List<T> list = new ArrayList<T>();
298 for (T x : iterable)
299 list.add(x);
300 Collections.<T>sort(list, comparator);
301 return list;
302 }
303
304 private static Comparator<PseudoElement> pseudoElementComparator = new Comparator<PseudoElement>() {
305 public int compare(PseudoElement e1, PseudoElement e2) {
306 return e1.toString().compareTo(e2.toString());
307 }
308 };
309
310
311
312 private static void insertStyle(StringBuilder builder, NodeData nodeData) {
313 List<String> properties = new ArrayList<String>(nodeData.getPropertyNames());
314 properties.remove("page");
315 Collections.sort(properties);
316 for (String prop : properties) {
317 String val = BrailleCssSerializer.serializePropertyValue(nodeData, prop, false);
318 if (val != null)
319 builder.append(prop).append(": ").append(val).append("; ");
320 }
321 }
322
323 private static void pseudoElementToString(StringBuilder builder, PseudoElement elem) {
324 if (elem instanceof PseudoElementImpl) {
325 builder.append("&").append(elem);
326 return; }
327 else {
328 builder.append("&::").append(elem.getName());
329 String[] args = elem.getArguments();
330 if (args.length > 0) {
331 StringBuilder s = new StringBuilder();
332 Iterator<String> it = Arrays.asList(args).iterator();
333 while (it.hasNext()) {
334 s.append(it.next());
335 if (it.hasNext()) s.append(", "); }
336 builder.append("(").append(s).append(")"); }}
337 }
338
339 private void insertPseudoStyle(StringBuilder builder, NodeData nodeData, PseudoElement elem,
340 Map<String,Map<String,RulePage>> pageRules) {
341 pseudoElementToString(builder, elem);
342 builder.append(" { ");
343 insertStyle(builder, nodeData);
344 Map<String,RulePage> pageRule = getPageRule(nodeData, pageRules);
345 if (pageRule != null)
346 insertPageStyle(builder, pageRule);
347 if (builder.substring(builder.length() - 2).equals("; "))
348 builder.replace(builder.length() - 2, builder.length(), " ");
349 builder.append("} ");
350 }
351
352 private void insertPageStyle(StringBuilder builder, Map<String,RulePage> pageRule) {
353 for (RulePage r : pageRule.values())
354 insertPageStyle(builder, r);
355 }
356
357 private void insertPageStyle(StringBuilder builder, RulePage pageRule) {
358 builder.append(BrailleCssSerializer.toString(pageRule, brailleCSSParser)).append(" ");
359 }
360
361 private Map<String,RulePage> getPageRule(NodeData nodeData, Map<String,Map<String,RulePage>> pageRules) {
362 BrailleCSSProperty.Page pageProperty; {
363 if (nodeData != null)
364 pageProperty = nodeData.<BrailleCSSProperty.Page>getProperty("page", false);
365 else
366 pageProperty = null;
367 }
368 String name; {
369 if (pageProperty != null) {
370 if (pageProperty == BrailleCSSProperty.Page.identifier)
371 name = nodeData.<TermIdent>getValue(TermIdent.class, "page", false).getValue();
372 else
373 name = pageProperty.toString(); }
374 else
375 name = null;
376 }
377 if (name != null)
378 return getPageRule(name, pageRules);
379 else
380 return null;
381 }
382
383 private Map<String,RulePage> getPageRule(String name, Map<String,Map<String,RulePage>> pageRules) {
384 Map<String,RulePage> auto = pageRules == null ? null : pageRules.get("auto");
385 Map<String,RulePage> named = null;
386 if (!name.equals("auto"))
387 named = pageRules.get(name);
388 Map<String,RulePage> result = new HashMap<String,RulePage>();
389 List<RulePage> from;
390 RulePage r;
391 Set<String> pseudos = new HashSet<String>();
392 if (named != null)
393 pseudos.addAll(named.keySet());
394 if (auto != null)
395 pseudos.addAll(auto.keySet());
396 for (String pseudo : pseudos) {
397 boolean noPseudo = "".equals(pseudo);
398 from = new ArrayList<RulePage>();
399 if (named != null) {
400 r = named.get(pseudo);
401 if (r != null) from.add(r);
402 if (!noPseudo) {
403 r = named.get("");
404 if (r != null) from.add(r); }}
405 if (auto != null) {
406 r = auto.get(pseudo);
407 if (r != null) from.add(r);
408 if (!noPseudo) {
409 r = auto.get("");
410 if (r != null) from.add(r); }}
411 result.put(pseudo, makePageRule(name, noPseudo ? null : pseudo, from)); }
412 return result;
413 }
414
415 private RulePage makePageRule(String name, String pseudo, List<RulePage> from) {
416 RulePage pageRule = brailleRuleFactory.createPage().setName(name).setPseudo(pseudo);
417 for (RulePage f : from)
418 for (Rule<?> r : f)
419 if (r instanceof Declaration) {
420 Declaration d = (Declaration)r;
421 String property = d.getProperty();
422 if (getDeclaration(pageRule, property) == null)
423 pageRule.add(r); }
424 else if (r instanceof RuleMargin) {
425 RuleMargin m = (RuleMargin)r;
426 String marginArea = m.getMarginArea();
427 RuleMargin marginRule = getRuleMargin(pageRule, marginArea);
428 if (marginRule == null) {
429 marginRule = brailleRuleFactory.createMargin(marginArea);
430 pageRule.add(marginRule);
431 marginRule.replaceAll(m); }
432 else
433 for (Declaration d : m)
434 if (getDeclaration(marginRule, d.getProperty()) == null)
435 marginRule.add(d); }
436 return pageRule;
437 }
438
439 private static Declaration getDeclaration(Collection<? extends Rule<?>> rule, String property) {
440 for (Declaration d : Iterables.filter(rule, Declaration.class))
441 if (d.getProperty().equals(property))
442 return d;
443 return null;
444 }
445
446 private static RuleMargin getRuleMargin(Collection<? extends Rule<?>> rule, String marginArea) {
447 for (RuleMargin m : Iterables.filter(rule, RuleMargin.class))
448 if (m.getMarginArea().equals(marginArea))
449 return m;
450 return null;
451 }
452
453 private void insertVolumeStyle(StringBuilder builder, Map<String,RuleVolume> volumeRule, Map<String,Map<String,RulePage>> pageRules) {
454 for (Map.Entry<String,RuleVolume> r : volumeRule.entrySet())
455 insertVolumeStyle(builder, r, pageRules);
456 }
457
458 private void insertVolumeStyle(StringBuilder builder, Map.Entry<String,RuleVolume> volumeRule, Map<String,Map<String,RulePage>> pageRules) {
459 builder.append("@volume");
460 String pseudo = volumeRule.getKey();
461 if (pseudo != null && !"".equals(pseudo))
462 builder.append(":").append(pseudo);
463 builder.append(" { ");
464 String declarations = BrailleCssSerializer.serializeDeclarationList(Iterables.filter(volumeRule.getValue(), Declaration.class));
465 if (!declarations.isEmpty())
466 builder.append(declarations).append("; ");
467 for (RuleVolumeArea volumeArea : Iterables.filter(volumeRule.getValue(), RuleVolumeArea.class))
468 insertVolumeAreaStyle(builder, volumeArea, pageRules);
469 if (builder.substring(builder.length() - 2).equals("; "))
470 builder.replace(builder.length() - 2, builder.length(), " ");
471 builder.append("} ");
472 }
473
474 private void insertVolumeAreaStyle(StringBuilder builder, RuleVolumeArea ruleVolumeArea, Map<String,Map<String,RulePage>> pageRules) {
475 builder.append("@").append(ruleVolumeArea.getVolumeArea().value).append(" { ");
476 StringBuilder innerStyle = new StringBuilder();
477 Map<String,RulePage> pageRule = null;
478 List<Declaration> declarations = new ArrayList<>();
479 for (Declaration decl : Iterables.filter(ruleVolumeArea, Declaration.class))
480 if ("page".equals(decl.getProperty())) {
481 StringBuilder s = new StringBuilder();
482 Iterator<Term<?>> it = decl.iterator();
483 while (it.hasNext()) {
484 s.append(BrailleCssSerializer.toString(it.next()));
485 if (it.hasNext()) s.append(" "); }
486 pageRule = getPageRule(s.toString(), pageRules); }
487 else
488 declarations.add(decl);
489 if (!declarations.isEmpty())
490 innerStyle.append(BrailleCssSerializer.serializeDeclarationList(declarations)).append("; ");
491 if (pageRule != null)
492 insertPageStyle(innerStyle, pageRule);
493 if (innerStyle.length() > 1 && innerStyle.substring(innerStyle.length() - 2).equals("; "))
494 innerStyle.replace(innerStyle.length() - 2, innerStyle.length(), " ");
495 builder.append(innerStyle).append("} ");
496 }
497
498 private static void insertTextTransformDefinition(StringBuilder builder, RuleTextTransform rule) {
499 builder.append("@text-transform");
500 String name = rule.getName();
501 if (name != null) builder.append(' ').append(name);
502 builder.append(" { ");
503 List<Declaration> declarationList = new ArrayList<>();
504 for (Declaration d : rule) {
505 if (d.size() == 1 && d.get(0) instanceof TermURI) {
506 TermURI term = (TermURI)d.get(0);
507 URI uri = URLs.asURI(term.getValue());
508 if (!uri.isAbsolute() && !uri.getSchemeSpecificPart().startsWith("/")) {
509
510
511 if (term.getBase() != null)
512 uri = URLs.resolve(URLs.asURI(term.getBase()), uri);
513 try {
514 new File(uri);
515 try {
516 uri = new URI("volatile-file", uri.getSchemeSpecificPart(), uri.getFragment());
517 } catch (URISyntaxException e) {
518 throw new IllegalStateException(e);
519 }
520 } catch (IllegalArgumentException e) {
521
522 }
523 d = createDeclaration(d.getProperty(), createTermURI(uri));
524 }
525 }
526 declarationList.add(d);
527 }
528 String declarations = BrailleCssSerializer.serializeDeclarationList(declarationList);
529 if (!declarations.isEmpty())
530 builder.append(declarations).append(" ");
531 builder.append("} ");
532 }
533
534 private static void insertHyphenationResourceDefinition(StringBuilder builder, RuleHyphenationResource rule) {
535 builder.append("@hyphenation-resource");
536 builder.append(":lang(").append(BrailleCssSerializer.serializeLanguageRanges(rule.getLanguageRanges())).append(")");
537 builder.append(" { ");
538 List<Declaration> declarationList = new ArrayList<>();
539 for (Declaration d : rule) {
540 if (d.size() == 1 && d.get(0) instanceof TermURI) {
541 TermURI term = (TermURI)d.get(0);
542 URI uri = URLs.asURI(term.getValue());
543 if (!uri.isAbsolute() && !uri.getSchemeSpecificPart().startsWith("/")) {
544
545
546 if (term.getBase() != null)
547 uri = URLs.resolve(URLs.asURI(term.getBase()), uri);
548 try {
549 new File(uri);
550 try {
551 uri = new URI("volatile-file", uri.getSchemeSpecificPart(), uri.getFragment());
552 } catch (URISyntaxException e) {
553 throw new IllegalStateException(e);
554 }
555 } catch (IllegalArgumentException e) {
556
557 }
558 try {
559 d = createDeclaration(d.getProperty(), createTermURI(uri));
560 } catch (RuntimeException e) {
561 e.printStackTrace();
562 throw e;
563 }
564 }
565 }
566 declarationList.add(d);
567 }
568 String declarations = BrailleCssSerializer.serializeDeclarationList(declarationList);
569 if (!declarations.isEmpty())
570 builder.append(declarations).append(" ");
571 builder.append("} ");
572 }
573
574 private static Declaration createDeclaration(String prop, Term<?> val) {
575 return new DeclarationImpl() {{
576 this.property = prop;
577 this.list = ImmutableList.of(val);
578 }};
579 }
580
581 private static TermURI createTermURI(URI uri) {
582 return new TermURIImpl() {{
583 this.value = uri.toString();
584 }};
585 }
586
587 private static void insertCounterStyleDefinition(StringBuilder builder, RuleCounterStyle rule) {
588 String name = rule.getName();
589 builder.append("@counter-style ").append(name).append(" { ");
590 String declarations = BrailleCssSerializer.serializeDeclarationList(rule);
591 if (!declarations.isEmpty())
592 builder.append(declarations).append(" ");
593 builder.append("} ");
594 }
595
596 private static void insertAtRule(StringBuilder builder, VendorAtRule<? extends Rule<?>> rule) {
597 builder.append("@").append(rule.getName()).append(" { ");
598 String declarations = BrailleCssSerializer.serializeDeclarationList(Iterables.filter(rule, Declaration.class));
599 if (!declarations.isEmpty())
600 builder.append(declarations).append("; ");
601 for (VendorAtRule<? extends Rule<?>> r : Iterables.filter(rule, VendorAtRule.class))
602 insertAtRule(builder, r);
603 if (builder.substring(builder.length() - 2).equals("; "))
604 builder.replace(builder.length() - 2, builder.length(), " ");
605 builder.append("} ");
606 }
607
608 private static Map<String,RuleVolume> getVolumeRule(String name, Map<String,Map<String,RuleVolume>> volumeRules) {
609 Map<String,RuleVolume> auto = volumeRules.get("auto");
610 Map<String,RuleVolume> named = null;
611 if (!name.equals("auto"))
612 named = volumeRules.get(name);
613 Map<String,RuleVolume> result = new HashMap<String,RuleVolume>();
614 List<RuleVolume> from;
615 RuleVolume r;
616 Set<String> pseudos = new HashSet<String>();
617 if (named != null)
618 pseudos.addAll(named.keySet());
619 if (auto != null)
620 pseudos.addAll(auto.keySet());
621
622
623
624
625 if (pseudos.contains("first") && pseudos.contains("last"))
626 pseudos.add("only");
627 for (String pseudo : pseudos) {
628 from = new ArrayList<RuleVolume>();
629 if (named != null) {
630 r = named.get(pseudo);
631 if (r != null) from.add(r);
632 if ("only".equals(pseudo)) {
633 r = named.get("first");
634 if (r != null) from.add(r);
635 r = named.get("last");
636 if (r != null) from.add(r); }
637 if (!"".equals(pseudo)) {
638 r = named.get("");
639 if (r != null) from.add(r); }}
640 if (auto != null) {
641 r = auto.get(pseudo);
642 if (r != null) from.add(r);
643 if ("only".equals(pseudo)) {
644 r = auto.get("first");
645 if (r != null) from.add(r);
646 r = auto.get("last");
647 if (r != null) from.add(r); }
648 if (!"".equals(pseudo)) {
649 r = auto.get("");
650 if (r != null) from.add(r); }}
651 result.put(pseudo,
652 makeVolumeRule(
653
654
655
656
657 ("".equals(pseudo) || "only".equals(pseudo)) ? null : pseudo,
658 from)); }
659 return result;
660 }
661
662 private static final Pattern FUNCTION = Pattern.compile("(nth|nth-last)\\(([1-9][0-9]*)\\)");
663
664 private static RuleVolume makeVolumeRule(String pseudo, List<RuleVolume> from) {
665 String arg = null;
666 if (pseudo != null) {
667 Matcher m = FUNCTION.matcher(pseudo);
668 if (m.matches()) {
669 pseudo = m.group(1);
670 arg = m.group(2); }}
671 RuleVolume volumeRule = new RuleVolume(pseudo, arg);
672 for (RuleVolume f : from)
673 for (Rule<?> r : f)
674 if (r instanceof Declaration) {
675 Declaration d = (Declaration)r;
676 String property = d.getProperty();
677 if (getDeclaration(volumeRule, property) == null)
678 volumeRule.add(r); }
679 else if (r instanceof RuleVolumeArea) {
680 RuleVolumeArea a = (RuleVolumeArea)r;
681 String volumeArea = a.getVolumeArea().value;
682 RuleVolumeArea volumeAreaRule = getRuleVolumeArea(volumeRule, volumeArea);
683 if (volumeAreaRule == null) {
684 volumeAreaRule = new RuleVolumeArea(volumeArea);
685 volumeRule.add(volumeAreaRule);
686 volumeAreaRule.replaceAll(a); }
687 else
688 for (Declaration d : Iterables.filter(a, Declaration.class))
689 if (getDeclaration(volumeAreaRule, d.getProperty()) == null)
690 volumeAreaRule.add(d); }
691 return volumeRule;
692 }
693
694 private static RuleVolumeArea getRuleVolumeArea(Collection<? extends Rule<?>> rule, String volumeArea) {
695 for (RuleVolumeArea m : Iterables.filter(rule, RuleVolumeArea.class))
696 if (m.getVolumeArea().value.equals(volumeArea))
697 return m;
698 return null;
699 }
700 }