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