001: /*
002:
003: ============================================================================
004: The Apache Software License, Version 1.1
005: ============================================================================
006:
007: Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.
008:
009: Redistribution and use in source and binary forms, with or without modifica-
010: tion, are permitted provided that the following conditions are met:
011:
012: 1. Redistributions of source code must retain the above copyright notice,
013: this list of conditions and the following disclaimer.
014:
015: 2. Redistributions in binary form must reproduce the above copyright notice,
016: this list of conditions and the following disclaimer in the documentation
017: and/or other materials provided with the distribution.
018:
019: 3. The end-user documentation included with the redistribution, if any, must
020: include the following acknowledgment: "This product includes software
021: developed by the Apache Software Foundation (http://www.apache.org/)."
022: Alternately, this acknowledgment may appear in the software itself, if
023: and wherever such third-party acknowledgments normally appear.
024:
025: 4. The names "Batik" and "Apache Software Foundation" must not be
026: used to endorse or promote products derived from this software without
027: prior written permission. For written permission, please contact
028: apache@apache.org.
029:
030: 5. Products derived from this software may not be called "Apache", nor may
031: "Apache" appear in their name, without prior written permission of the
032: Apache Software Foundation.
033:
034: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
035: INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
036: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
037: APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
038: INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU-
039: DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
040: OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
041: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
042: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
043: THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
044:
045: This software consists of voluntary contributions made by many individuals
046: on behalf of the Apache Software Foundation. For more information on the
047: Apache Software Foundation, please see <http://www.apache.org/>.
048:
049: */
050:
051: package org.apache.batik.css.engine;
052:
053: import org.w3c.css.sac.SACMediaList; // <rave>
054: import java.util.ArrayList;
055: import java.util.HashMap;
056: import java.util.Iterator;
057: import java.util.List;
058: import java.util.Map;
059: import org.apache.batik.css.engine.sac.CSSAndCondition;
060: import org.apache.batik.css.engine.sac.CSSAttributeCondition;
061: import org.apache.batik.css.engine.sac.CSSChildSelector;
062: import org.apache.batik.css.engine.sac.CSSClassCondition;
063: import org.apache.batik.css.engine.sac.CSSConditionalSelector;
064: import org.apache.batik.css.engine.sac.CSSDescendantSelector;
065: import org.apache.batik.css.engine.sac.CSSDirectAdjacentSelector;
066: import org.apache.batik.css.engine.sac.CSSElementSelector;
067: import org.apache.batik.css.engine.sac.CSSIdCondition;
068: import org.apache.batik.css.engine.sac.CSSOneOfAttributeCondition;
069: import org.apache.batik.css.engine.sac.CSSPseudoElementSelector;
070: import org.apache.batik.css.engine.sac.ExtendedSelector;
071: import org.w3c.css.sac.Condition;
072: import org.w3c.css.sac.NegativeSelector;
073: import org.w3c.css.sac.Selector;
074: import org.w3c.css.sac.SelectorList;
075: import org.w3c.css.sac.SimpleSelector;
076:
077: // </rave>
078:
079: /**
080: * This class represents a list of rules.
081: *
082: * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
083: * @version $Id$
084: */
085: public class StyleSheet {
086:
087: /**
088: * The rules.
089: */
090: // <rave>
091: // TODO: Bump up the rule count so I don't have to grow it when dealing with the braveheart stylesheet
092: // </rave>
093: protected Rule[] rules = new Rule[16];
094:
095: /**
096: * The number of rules.
097: */
098: protected int size;
099:
100: /**
101: * The parent sheet, if any.
102: */
103: protected StyleSheet parent;
104:
105: /**
106: * Whether or not this stylesheet is alternate.
107: */
108: protected boolean alternate;
109:
110: /**
111: * The media to use to cascade properties.
112: */
113: protected SACMediaList media;
114:
115: /**
116: * The style sheet title.
117: */
118: protected String title;
119:
120: /**
121: * Sets the media to use to compute the styles.
122: */
123: public void setMedia(SACMediaList m) {
124: media = m;
125: }
126:
127: /**
128: * Returns the media to use to compute the styles.
129: */
130: public SACMediaList getMedia() {
131: return media;
132: }
133:
134: /**
135: * Returns the parent sheet.
136: */
137: public StyleSheet getParent() {
138: return parent;
139: }
140:
141: /**
142: * Sets the parent sheet.
143: */
144: public void setParent(StyleSheet ss) {
145: parent = ss;
146: }
147:
148: /**
149: * Sets the 'alternate' attribute of this style-sheet.
150: */
151: public void setAlternate(boolean b) {
152: alternate = b;
153: }
154:
155: /**
156: * Tells whether or not this stylesheet is alternate.
157: */
158: public boolean isAlternate() {
159: return alternate;
160: }
161:
162: /**
163: * Sets the 'title' attribute of this style-sheet.
164: */
165: public void setTitle(String t) {
166: title = t;
167: }
168:
169: /**
170: * Returns the title of this style-sheet.
171: */
172: public String getTitle() {
173: return title;
174: }
175:
176: /**
177: * Returns the number of rules.
178: */
179: public int getSize() {
180: return size;
181: }
182:
183: /**
184: * Returns the rule at the given index.
185: */
186: public Rule getRule(int i) {
187: return rules[i];
188: }
189:
190: /**
191: * Clears the content.
192: */
193: public void clear() {
194: size = 0;
195: rules = new Rule[10];
196: }
197:
198: /**
199: * Appends a rule to the stylesheet.
200: */
201: public void append(Rule r) {
202: if (size == rules.length) {
203: Rule[] t = new Rule[size * 2];
204: for (int i = 0; i < size; i++) {
205: t[i] = rules[i];
206: }
207: rules = t;
208: }
209: rules[size++] = r;
210: }
211:
212: /**
213: * Returns a printable representation of this style-sheet.
214: */
215: public String toString(CSSEngine eng) {
216: StringBuffer sb = new StringBuffer();
217: for (int i = 0; i < size; i++) {
218: sb.append(rules[i].toString(eng));
219: }
220: return sb.toString();
221: }
222:
223: // <rave>
224: // BEGIN RAVE MODIFICATIONS
225: public String toString() {
226: return super .toString() + "[" + getTitle() + "]";
227: }
228:
229: // Support for rule filtering optimization. See CSSEngine.addMatchingRules for details.
230: // For rule filtering
231: private HashMap tagMap;
232: private HashMap classMap;
233: private HashMap idMap;
234: private Rule[] remainingRules;
235:
236: /** Return a Map which maps from tag names to ExtendedSelector[] of potential rules
237: * that could match an element of the given tag name (obviously the rules
238: * may specify additional constraints such as descendant selectors etc. that
239: * must be checked.)
240: */
241: public HashMap getTagMap() {
242: return tagMap;
243: }
244:
245: /** Return a Map which maps from class attribute values to ExtendedSelector[] of potential rules
246: * that could match an element which has a class attribute including the given style
247: * class name. (obviously the rules may specify additional constraints such as
248: * descendant selectors etc. that must be checked.)
249: */
250: public HashMap getClassMap() {
251: return classMap;
252: }
253:
254: /** Return a Map which maps from element ids to ExtendedSelector[] of potential rules
255: * that could match an element with the given id attribute (obviously the rules
256: * may specify additional constraints such as descendant selectors etc. that
257: * must be checked.)
258: */
259: public HashMap getIdMap() {
260: return idMap;
261: }
262:
263: /** Return an array of rules that do not fall into the tag, class or id list of
264: * rules. All of these rules must be checked against an element since they couldn't
265: * be filtered out based on simple criteria.
266: * @todo Remove ExtendedSelectors from the Rule's selector list that are covered
267: * by the other cases. But it turns out that for stylesheets I've looked at, even
268: * moderately complex ones like the braveheart ones, there aren't many of these
269: * so not much would be gained by going to this trouble
270: */
271: public Rule[] getRemainingRules() {
272: return remainingRules;
273: }
274:
275: private boolean addCondition(Condition condition, Map tagMap,
276: Map classMap, Map idMap, List otherList, Rule rule,
277: ExtendedSelector extSelector) {
278: switch (condition.getConditionType()) {
279: case Condition.SAC_AND_CONDITION: {
280: Condition first = ((CSSAndCondition) condition)
281: .getFirstCondition();
282: Condition second = ((CSSAndCondition) condition)
283: .getSecondCondition();
284: // Note short-circuit evaluation
285: return addCondition(first, tagMap, classMap, idMap,
286: otherList, rule, extSelector)
287: || addCondition(second, tagMap, classMap, idMap,
288: otherList, rule, extSelector);
289: }
290: /*
291: case Condition.SAC_OR_CONDITION: {
292: Condition first = ((CSSCondition)condition).getFirstCondition();
293: Condition second = ((CSSOrCondition)condition).getSecondCondition();
294: // Note short-circuit evaluation
295: //return addCondition(first, tagMap, classMap, idMap, otherList, rule, extSelector) ||
296: // addCondition(second, tagMap, classMap, idMap, otherList, rule, extSelector);
297: }
298: */
299: case Condition.SAC_CLASS_CONDITION: {
300: String styleClass = ((CSSClassCondition) condition)
301: .getValue();
302: List list = (List) classMap.get(styleClass);
303: if (list == null) {
304: list = new ArrayList(30);
305: classMap.put(styleClass, list);
306: }
307: list.add(extSelector);
308: return true;
309: }
310: case Condition.SAC_ATTRIBUTE_CONDITION: {
311: String localName = ((CSSAttributeCondition) condition)
312: .getLocalName();
313: if (localName.equals("class")) {
314: String styleClass = ((CSSAttributeCondition) condition)
315: .getValue();
316: List list = (List) classMap.get(styleClass);
317: if (list == null) {
318: list = new ArrayList(30);
319: classMap.put(styleClass, list);
320: }
321: list.add(extSelector);
322: return true;
323: } else {
324: otherList.add(rule);
325: return false;
326: }
327: }
328: case Condition.SAC_ID_CONDITION: {
329: String id = ((CSSIdCondition) condition).getValue();
330: List list = (List) idMap.get(id);
331: if (list == null) {
332: list = new ArrayList(30);
333: idMap.put(id, list);
334: }
335: list.add(extSelector);
336: return true;
337: }
338: case Condition.SAC_ONE_OF_ATTRIBUTE_CONDITION: {
339: String localName = ((CSSOneOfAttributeCondition) condition)
340: .getLocalName();
341: if (localName.equals("class")) {
342: String styleClass = ((CSSOneOfAttributeCondition) condition)
343: .getValue();
344: List list = (List) classMap.get(styleClass);
345: if (list == null) {
346: list = new ArrayList(30);
347: classMap.put(styleClass, list);
348: }
349: list.add(extSelector);
350: return true;
351: } else {
352: otherList.add(rule);
353: return false;
354: }
355: }
356: default:
357: otherList.add(rule);
358: return false;
359: }
360:
361: }
362:
363: /**
364: * Add the selector to one of the tag map, class map, id map, or the list of other unknown rules.
365: * @return True iff the rule was added to one of the maps (e.g. not the otherList).
366: */
367: private boolean addSelector(Selector selector, Map tagMap,
368: Map classMap, Map idMap, List otherList, Rule rule,
369: ExtendedSelector extSelector) {
370: switch (selector.getSelectorType()) {
371: /**
372: * This is a conditional selector.
373: * example:
374: * <pre class="example">
375: * simple[role="private"]
376: * .part1
377: * H1#myId
378: * P:lang(fr).p1
379: * </pre>
380: *
381: * @see ConditionalSelector
382: */
383: case Selector.SAC_CONDITIONAL_SELECTOR: {
384: Condition condition = ((CSSConditionalSelector) selector)
385: .getCondition();
386: boolean added = addCondition(condition, tagMap, classMap,
387: idMap, otherList, rule, extSelector);
388: if (added) {
389: return true;
390: }
391: SimpleSelector simple = ((CSSConditionalSelector) selector)
392: .getSimpleSelector();
393: added = addSelector(simple, tagMap, classMap, idMap,
394: otherList, rule, extSelector); // recurse
395: if (added) {
396: return true;
397: }
398: otherList.add(rule);
399: return false;
400: }
401:
402: /**
403: * This selector matches any node.
404: * @see SimpleSelector
405: */
406: case Selector.SAC_ANY_NODE_SELECTOR:
407: // Rules which match all cannot be limited.... right?
408: otherList.add(rule);
409: return false;
410:
411: /**
412: * This selector matches the root node.
413: * @see SimpleSelector
414: */
415: case Selector.SAC_ROOT_NODE_SELECTOR:
416: // XXX Decide if I need to worry about this one
417: otherList.add(rule);
418: return false;
419:
420: /**
421: * This selector matches only node that are different from a specified one.
422: * @see NegativeSelector
423: */
424: case Selector.SAC_NEGATIVE_SELECTOR: {
425: SimpleSelector simple = ((NegativeSelector) selector)
426: .getSimpleSelector();
427: addSelector(simple, tagMap, classMap, idMap, otherList,
428: rule, extSelector); // recurse
429: return false;
430: }
431:
432: /**
433: * This selector matches only element node.
434: * example:
435: * <pre class="example">
436: * H1
437: * animate
438: * </pre>
439: * @see ElementSelector
440: */
441: case Selector.SAC_ELEMENT_NODE_SELECTOR: {
442: String tag = ((CSSElementSelector) selector).getLocalName();
443: if (tag != null) {
444: tag = tag.toLowerCase();
445: List list = (List) tagMap.get(tag);
446: if (list == null) {
447: list = new ArrayList(30);
448: tagMap.put(tag, list);
449: }
450: list.add(extSelector);
451: }
452: return true;
453: }
454:
455: /**
456: * This selector matches only text node.
457: * @see CharacterDataSelector
458: */
459: case Selector.SAC_TEXT_NODE_SELECTOR:
460: // XXX Decide if I need to worry about this one
461: otherList.add(rule);
462: return false;
463:
464: /**
465: * This selector matches only cdata node.
466: * @see CharacterDataSelector
467: */
468: case Selector.SAC_CDATA_SECTION_NODE_SELECTOR:
469: otherList.add(rule);
470: return false;
471:
472: /**
473: * This selector matches only processing instruction node.
474: * @see ProcessingInstructionSelector
475: */
476: case Selector.SAC_PROCESSING_INSTRUCTION_NODE_SELECTOR:
477: otherList.add(rule);
478: return false;
479:
480: /**
481: * This selector matches only comment node.
482: * @see CharacterDataSelector
483: */
484: case Selector.SAC_COMMENT_NODE_SELECTOR:
485: otherList.add(rule);
486: return false;
487:
488: /**
489: * This selector matches the 'first line' pseudo element.
490: * example:
491: * <pre class="example">
492: * :first-line
493: * </pre>
494: * @see ElementSelector
495: */
496: case Selector.SAC_PSEUDO_ELEMENT_SELECTOR: {
497: String tag = ((CSSPseudoElementSelector) selector)
498: .getLocalName();
499: if (tag != null) {
500: tag = tag.toLowerCase();
501: List list = (List) tagMap.get(tag);
502: if (list == null) {
503: list = new ArrayList(30);
504: tagMap.put(tag, list);
505: }
506: list.add(extSelector);
507: }
508: return true;
509: }
510:
511: /* combinator selectors */
512:
513: /**
514: * This selector matches an arbitrary descendant of some ancestor element.
515: * example:
516: * <pre class="example">
517: * E F
518: * </pre>
519: * @see DescendantSelector
520: */
521: case Selector.SAC_DESCENDANT_SELECTOR: {
522: SimpleSelector simple = ((CSSDescendantSelector) selector)
523: .getSimpleSelector();
524: return addSelector(simple, tagMap, classMap, idMap,
525: otherList, rule, extSelector); // recurse
526: }
527:
528: /**
529: * This selector matches a childhood relationship between two elements.
530: * example:
531: * <pre class="example">
532: * E > F
533: * </pre>
534: * @see DescendantSelector
535: */
536: case Selector.SAC_CHILD_SELECTOR: {
537: SimpleSelector simple = ((CSSChildSelector) selector)
538: .getSimpleSelector();
539: return addSelector(simple, tagMap, classMap, idMap,
540: otherList, rule, extSelector); // recurse
541: }
542:
543: /**
544: * This selector matches two selectors who shared the same parent in the
545: * document tree and the element represented by the first sequence
546: * immediately precedes the element represented by the second one.
547: * example:
548: * <pre class="example">
549: * E + F
550: * </pre>
551: * @see SiblingSelector
552: */
553: case Selector.SAC_DIRECT_ADJACENT_SELECTOR: {
554: //otherList.add(r);
555: //break;
556: // XXX Figure out if this works right...
557: SimpleSelector simple = ((CSSDirectAdjacentSelector) selector)
558: .getSiblingSelector();
559: return addSelector(simple, tagMap, classMap, idMap,
560: otherList, rule, extSelector); // recurse
561: }
562: }
563:
564: return false;
565: }
566:
567: /** Process the full set of rules in this stylesheet and try to set up the rule filter
568: * data structures such that the tag map, class map, etc. maps can be used to quickly
569: * match elements of given criteria. This should only be done once for a stylesheet.
570: */
571: public void setupFilters() {
572: if (!CSSEngine.RULE_FILTERING) {
573: return;
574: }
575:
576: //long start = System.currentTimeMillis();
577:
578: int len = getSize();
579: tagMap = new HashMap(2 * len);
580: classMap = new HashMap(2 * len);
581: idMap = new HashMap(2 * len);
582: ArrayList otherList = new ArrayList(100);
583:
584: for (int i = 0; i < len; i++) {
585: Rule r = getRule(i);
586: switch (r.getType()) {
587: case StyleRule.TYPE:
588: StyleRule style = (StyleRule) r;
589: style.setPosition(i);
590: SelectorList sl = style.getSelectorList();
591: int slen = sl.getLength();
592: for (int j = 0; j < slen; j++) {
593: ExtendedSelector s = (ExtendedSelector) sl.item(j);
594: addSelector(s, tagMap, classMap, idMap, otherList,
595: r, s);
596: s.setRule(r);
597: }
598: break;
599:
600: case MediaRule.TYPE:
601: case ImportRule.TYPE:
602: // XXX TODO FIXME I should just recursively add these rules into the
603: // maps too such that I later don't have to worry about it. E.g. the maps
604: // should be fully transitive.
605:
606: otherList.add(r);
607: break;
608: }
609: }
610:
611: // Replace the List objects in the map with actual ExtendedSelector[] such that the hot code which
612: // -uses- these datastructures can do so quickly and with less casting etc. (Plus
613: // this means we'll use less memory since we'll only hold what we need)
614: Iterator it = tagMap.entrySet().iterator();
615: while (it.hasNext()) {
616: Map.Entry entry = (Map.Entry) it.next();
617: List list = (List) entry.getValue();
618: ExtendedSelector[] array = (ExtendedSelector[]) list
619: .toArray(new ExtendedSelector[list.size()]);
620: entry.setValue(array);
621: }
622: it = classMap.entrySet().iterator();
623: while (it.hasNext()) {
624: Map.Entry entry = (Map.Entry) it.next();
625: List list = (List) entry.getValue();
626: ExtendedSelector[] array = (ExtendedSelector[]) list
627: .toArray(new ExtendedSelector[list.size()]);
628: entry.setValue(array);
629: }
630: it = idMap.entrySet().iterator();
631: while (it.hasNext()) {
632: Map.Entry entry = (Map.Entry) it.next();
633: List list = (List) entry.getValue();
634: ExtendedSelector[] array = (ExtendedSelector[]) list
635: .toArray(new ExtendedSelector[list.size()]);
636: entry.setValue(array);
637: }
638:
639: // Look for duplicates
640: Object prev = null;
641: int unique = 0;
642: for (int i = 0, n = otherList.size(); i < n; i++) {
643: Object next = otherList.get(i);
644: if (next != prev) {
645: unique++;
646: prev = next;
647: }
648: }
649:
650: if (unique != otherList.size()) {
651: remainingRules = new Rule[unique];
652: prev = null;
653: int index = 0;
654: for (int i = 0, n = otherList.size(); i < n; i++) {
655: Object next = otherList.get(i);
656: if (next != prev) {
657: remainingRules[index] = (Rule) next;
658: assert index == 0
659: || !(remainingRules[index] instanceof StyleRule)
660: || !(remainingRules[index - 1] instanceof StyleRule)
661: || ((StyleRule) remainingRules[index])
662: .getPosition() > ((StyleRule) remainingRules[index - 1])
663: .getPosition();
664: prev = next;
665: index++;
666: }
667: }
668: assert index == unique;
669: } else {
670: remainingRules = (Rule[]) otherList
671: .toArray(new Rule[otherList.size()]);
672: }
673:
674: //if (CSSEngine.DEBUG_FILTERING) {
675: // long end = System.currentTimeMillis();
676: // long delay = end-start;
677: // float seconds = delay/1000.0f;
678: // System.err.println("INITIALIZING STYLESHEET took " + delay + " milliseconds = " + seconds + "s");
679: // for (int i = 0; i < remainingRules.length; i++) {
680: // System.out.println("otherrule " + i + ": " + remainingRules[i]);
681: // }
682: //}
683: }
684:
685: // END RAVE MODIFICATIONS
686: // </rave>
687: }
|