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