KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > text > html > StyleSheet


1 /*
2  * @(#)StyleSheet.java 1.85 04/09/14
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7 package javax.swing.text.html;
8
9 import com.sun.java.swing.SwingUtilities2;
10 import java.util.*;
11 import java.awt.*;
12 import java.io.*;
13 import java.net.*;
14 import javax.swing.Icon JavaDoc;
15 import javax.swing.ImageIcon JavaDoc;
16 import javax.swing.border.*;
17 import javax.swing.event.ChangeListener JavaDoc;
18 import javax.swing.text.*;
19
20 /**
21  * Support for defining the visual characteristics of
22  * HTML views being rendered. The StyleSheet is used to
23  * translate the HTML model into visual characteristics.
24  * This enables views to be customized by a look-and-feel,
25  * multiple views over the same model can be rendered
26  * differently, etc. This can be thought of as a CSS
27  * rule repository. The key for CSS attributes is an
28  * object of type CSS.Attribute. The type of the value
29  * is up to the StyleSheet implementation, but the
30  * <code>toString</code> method is required
31  * to return a string representation of CSS value.
32  * <p>
33  * The primary entry point for HTML View implementations
34  * to get their attributes is the
35  * <a HREF="#getViewAttributes">getViewAttributes</a>
36  * method. This should be implemented to establish the
37  * desired policy used to associate attributes with the view.
38  * Each HTMLEditorKit (i.e. and therefore each associated
39  * JEditorPane) can have its own StyleSheet, but by default one
40  * sheet will be shared by all of the HTMLEditorKit instances.
41  * HTMLDocument instance can also have a StyleSheet, which
42  * holds the document-specific CSS specifications.
43  * <p>
44  * In order for Views to store less state and therefore be
45  * more lightweight, the StyleSheet can act as a factory for
46  * painters that handle some of the rendering tasks. This allows
47  * implementations to determine what they want to cache
48  * and have the sharing potentially at the level that a
49  * selector is common to multiple views. Since the StyleSheet
50  * may be used by views over multiple documents and typically
51  * the HTML attributes don't effect the selector being used,
52  * the potential for sharing is significant.
53  * <p>
54  * The rules are stored as named styles, and other information
55  * is stored to translate the context of an element to a
56  * rule quickly. The following code fragment will display
57  * the named styles, and therefore the CSS rules contained.
58  * <code><pre>
59  * &nbsp;
60  * &nbsp; import java.util.*;
61  * &nbsp; import javax.swing.text.*;
62  * &nbsp; import javax.swing.text.html.*;
63  * &nbsp;
64  * &nbsp; public class ShowStyles {
65  * &nbsp;
66  * &nbsp; public static void main(String[] args) {
67  * &nbsp; HTMLEditorKit kit = new HTMLEditorKit();
68  * &nbsp; HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
69  * &nbsp; StyleSheet styles = doc.getStyleSheet();
70  * &nbsp;
71  * &nbsp; Enumeration rules = styles.getStyleNames();
72  * &nbsp; while (rules.hasMoreElements()) {
73  * &nbsp; String name = (String) rules.nextElement();
74  * &nbsp; Style rule = styles.getStyle(name);
75  * &nbsp; System.out.println(rule.toString());
76  * &nbsp; }
77  * &nbsp; System.exit(0);
78  * &nbsp; }
79  * &nbsp; }
80  * &nbsp;
81  * </pre></code>
82  * <p>
83  * The semantics for when a CSS style should overide visual attributes
84  * defined by an element are not well defined. For example, the html
85  * <code>&lt;body bgcolor=red&gt;</code> makes the body have a red
86  * background. But if the html file also contains the CSS rule
87  * <code>body { background: blue }</code> it becomes less clear as to
88  * what color the background of the body should be. The current
89  * implemention gives visual attributes defined in the element the
90  * highest precedence, that is they are always checked before any styles.
91  * Therefore, in the previous example the background would have a
92  * red color as the body element defines the background color to be red.
93  * <p>
94  * As already mentioned this supports CSS. We don't support the full CSS
95  * spec. Refer to the javadoc of the CSS class to see what properties
96  * we support. The two major CSS parsing related
97  * concepts we do not currently
98  * support are pseudo selectors, such as <code>A:link { color: red }</code>,
99  * and the <code>important</code> modifier.
100  * <p>
101  * <font color="red">Note: This implementation is currently
102  * incomplete. It can be replaced with alternative implementations
103  * that are complete. Future versions of this class will provide
104  * better CSS support.</font>
105  *
106  * @author Timothy Prinzing
107  * @author Sunita Mani
108  * @author Sara Swanson
109  * @author Jill Nakata
110  * @version 1.85 09/14/04
111  */

112 public class StyleSheet extends StyleContext {
113     // As the javadoc states, this class maintains a mapping between
114
// a CSS selector (such as p.bar) and a Style.
115
// This consists of a number of parts:
116
// . Each selector is broken down into its constituent simple selectors,
117
// and stored in an inverted graph, for example:
118
// p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt }
119
// results in the graph:
120
// root
121
// |
122
// p
123
// / \
124
// ol ul
125
// each node (an instance of SelectorMapping) has an associated
126
// specificity and potentially a Style.
127
// . Every rule that is asked for (either by way of getRule(String) or
128
// getRule(HTML.Tag, Element)) results in a unique instance of
129
// ResolvedStyle. ResolvedStyles contain the AttributeSets from the
130
// SelectorMapping.
131
// . When a new rule is created it is inserted into the graph, and
132
// the AttributeSets of each ResolvedStyles are updated appropriately.
133
// . This class creates special AttributeSets, LargeConversionSet and
134
// SmallConversionSet, that maintain a mapping between StyleConstants
135
// and CSS so that developers that wish to use the StyleConstants
136
// methods can do so.
137
// . When one of the AttributeSets is mutated by way of a
138
// StyleConstants key, all the associated CSS keys are removed. This is
139
// done so that the two representations don't get out of sync. For
140
// example, if the developer adds StyleConsants.BOLD, FALSE to an
141
// AttributeSet that contains HTML.Tag.B, the HTML.Tag.B entry will
142
// be removed.
143

144     /**
145      * Construct a StyleSheet
146      */

147     public StyleSheet() {
148     super();
149     selectorMapping = new SelectorMapping(0);
150     resolvedStyles = new Hashtable();
151     if (css == null) {
152         css = new CSS JavaDoc();
153     }
154     }
155
156     /**
157      * Fetches the style to use to render the given type
158      * of HTML tag. The element given is representing
159      * the tag and can be used to determine the nesting
160      * for situations where the attributes will differ
161      * if nesting inside of elements.
162      *
163      * @param t the type to translate to visual attributes
164      * @param e the element representing the tag; the element
165      * can be used to determine the nesting for situations where
166      * the attributes will differ if nested inside of other
167      * elements
168      * @return the set of CSS attributes to use to render
169      * the tag
170      */

171     public Style getRule(HTML.Tag JavaDoc t, Element e) {
172         SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
173
174         try {
175             // Build an array of all the parent elements.
176
Vector searchContext = sb.getVector();
177
178             for (Element p = e; p != null; p = p.getParentElement()) {
179                 searchContext.addElement(p);
180             }
181
182             // Build a fully qualified selector.
183
int n = searchContext.size();
184             StringBuffer JavaDoc cacheLookup = sb.getStringBuffer();
185             AttributeSet attr;
186             String JavaDoc eName;
187             Object JavaDoc name;
188
189             // >= 1 as the HTML.Tag for the 0th element is passed in.
190
for (int counter = n - 1; counter >= 1; counter--) {
191                 e = (Element)searchContext.elementAt(counter);
192                 attr = e.getAttributes();
193                 name = attr.getAttribute(StyleConstants.NameAttribute);
194         eName = name.toString();
195                 cacheLookup.append(eName);
196                 if (attr != null) {
197                     if (attr.isDefined(HTML.Attribute.ID)) {
198                         cacheLookup.append('#');
199                         cacheLookup.append(attr.getAttribute
200                        (HTML.Attribute.ID));
201                     }
202                     else if (attr.isDefined(HTML.Attribute.CLASS)) {
203                         cacheLookup.append('.');
204                         cacheLookup.append(attr.getAttribute
205                        (HTML.Attribute.CLASS));
206                     }
207                 }
208                 cacheLookup.append(' ');
209             }
210             cacheLookup.append(t.toString());
211         e = (Element)searchContext.elementAt(0);
212         attr = e.getAttributes();
213         if (e.isLeaf()) {
214         // For leafs, we use the second tier attributes.
215
Object JavaDoc testAttr = attr.getAttribute(t);
216         if (testAttr instanceof AttributeSet) {
217             attr = (AttributeSet)testAttr;
218         }
219         else {
220             attr = null;
221         }
222         }
223             if (attr != null) {
224                 if (attr.isDefined(HTML.Attribute.ID)) {
225                     cacheLookup.append('#');
226                     cacheLookup.append(attr.getAttribute(HTML.Attribute.ID));
227                 }
228                 else if (attr.isDefined(HTML.Attribute.CLASS)) {
229                     cacheLookup.append('.');
230                     cacheLookup.append(attr.getAttribute
231                        (HTML.Attribute.CLASS));
232                 }
233             }
234
235             Style style = getResolvedStyle(cacheLookup.toString(),
236                        searchContext, t);
237         return style;
238         }
239         finally {
240             SearchBuffer.releaseSearchBuffer(sb);
241         }
242     }
243
244     /**
245      * Fetches the rule that best matches the selector given
246      * in string form. Where <code>selector</code> is a space separated
247      * String of the element names. For example, <code>selector</code>
248      * might be 'html body tr td''<p>
249      * The attributes of the returned Style will change
250      * as rules are added and removed. That is if you to ask for a rule
251      * with a selector "table p" and a new rule was added with a selector
252      * of "p" the returned Style would include the new attributes from
253      * the rule "p".
254      */

255     public Style getRule(String JavaDoc selector) {
256     selector = cleanSelectorString(selector);
257     if (selector != null) {
258         Style style = getResolvedStyle(selector);
259         return style;
260     }
261     return null;
262     }
263
264     /**
265      * Adds a set of rules to the sheet. The rules are expected to
266      * be in valid CSS format. Typically this would be called as
267      * a result of parsing a &lt;style&gt; tag.
268      */

269     public void addRule(String JavaDoc rule) {
270     if (rule != null) {
271             //tweaks to control display properties
272
//see BasicEditorPaneUI
273
final String JavaDoc baseUnitsDisable = "BASE_SIZE_DISABLE";
274             final String JavaDoc baseUnits = "BASE_SIZE ";
275             final String JavaDoc w3cLengthUnitsEnable = "W3C_LENGTH_UNITS_ENABLE";
276             final String JavaDoc w3cLengthUnitsDisable = "W3C_LENGTH_UNITS_DISABLE";
277             if (rule == baseUnitsDisable) {
278                 sizeMap = sizeMapDefault;
279             } else if (rule.startsWith(baseUnits)) {
280                 rebaseSizeMap(Integer.
281                               parseInt(rule.substring(baseUnits.length())));
282             } else if (rule == w3cLengthUnitsEnable) {
283                 w3cLengthUnits = true;
284             } else if (rule == w3cLengthUnitsDisable) {
285                 w3cLengthUnits = false;
286             } else {
287                 CssParser parser = new CssParser();
288                 try {
289                     parser.parse(getBase(), new StringReader(rule), false, false);
290                 } catch (IOException ioe) { }
291             }
292     }
293     }
294
295     /**
296      * Translates a CSS declaration to an AttributeSet that represents
297      * the CSS declaration. Typically this would be called as a
298      * result of encountering an HTML style attribute.
299      */

300     public AttributeSet getDeclaration(String JavaDoc decl) {
301     if (decl == null) {
302         return SimpleAttributeSet.EMPTY;
303     }
304     CssParser parser = new CssParser();
305     return parser.parseDeclaration(decl);
306     }
307
308     /**
309      * Loads a set of rules that have been specified in terms of
310      * CSS1 grammar. If there are collisions with existing rules,
311      * the newly specified rule will win.
312      *
313      * @param in the stream to read the CSS grammar from
314      * @param ref the reference URL. This value represents the
315      * location of the stream and may be null. All relative
316      * URLs specified in the stream will be based upon this
317      * parameter.
318      */

319     public void loadRules(Reader in, URL ref) throws IOException {
320     CssParser parser = new CssParser();
321     parser.parse(ref, in, false, false);
322     }
323
324     /**
325      * Fetches a set of attributes to use in the view for
326      * displaying. This is basically a set of attributes that
327      * can be used for View.getAttributes.
328      */

329     public AttributeSet getViewAttributes(View v) {
330     return new ViewAttributeSet(v);
331     }
332
333     /**
334      * Removes a named style previously added to the document.
335      *
336      * @param nm the name of the style to remove
337      */

338     public void removeStyle(String JavaDoc nm) {
339     Style aStyle = getStyle(nm);
340
341     if (aStyle != null) {
342         String JavaDoc selector = cleanSelectorString(nm);
343         String JavaDoc[] selectors = getSimpleSelectors(selector);
344         synchronized(this) {
345         SelectorMapping mapping = getRootSelectorMapping();
346         for (int i = selectors.length - 1; i >= 0; i--) {
347             mapping = mapping.getChildSelectorMapping(selectors[i],
348                                                               true);
349         }
350         Style rule = mapping.getStyle();
351         if (rule != null) {
352             mapping.setStyle(null);
353             if (resolvedStyles.size() > 0) {
354             Enumeration values = resolvedStyles.elements();
355             while (values.hasMoreElements()) {
356                 ResolvedStyle style = (ResolvedStyle)values.
357                                     nextElement();
358                 style.removeStyle(rule);
359             }
360             }
361         }
362         }
363     }
364     super.removeStyle(nm);
365     }
366
367     /**
368      * Adds the rules from the StyleSheet <code>ss</code> to those of
369      * the receiver. <code>ss's</code> rules will override the rules of
370      * any previously added style sheets. An added StyleSheet will never
371      * override the rules of the receiving style sheet.
372      *
373      * @since 1.3
374      */

375     public void addStyleSheet(StyleSheet JavaDoc ss) {
376     synchronized(this) {
377         if (linkedStyleSheets == null) {
378         linkedStyleSheets = new Vector();
379         }
380         if (!linkedStyleSheets.contains(ss)) {
381                 int index = 0;
382                 if (ss instanceof javax.swing.plaf.UIResource JavaDoc
383                     && linkedStyleSheets.size() > 1) {
384                     index = linkedStyleSheets.size() - 1;
385                 }
386         linkedStyleSheets.insertElementAt(ss, index);
387         linkStyleSheetAt(ss, index);
388         }
389     }
390     }
391
392     /**
393      * Removes the StyleSheet <code>ss</code> from those of the receiver.
394      *
395      * @since 1.3
396      */

397     public void removeStyleSheet(StyleSheet JavaDoc ss) {
398     synchronized(this) {
399         if (linkedStyleSheets != null) {
400         int index = linkedStyleSheets.indexOf(ss);
401         if (index != -1) {
402             linkedStyleSheets.removeElementAt(index);
403             unlinkStyleSheet(ss, index);
404             if (index == 0 && linkedStyleSheets.size() == 0) {
405             linkedStyleSheets = null;
406             }
407         }
408         }
409     }
410     }
411
412     //
413
// The following is used to import style sheets.
414
//
415

416     /**
417      * Returns an array of the linked StyleSheets. Will return null
418      * if there are no linked StyleSheets.
419      *
420      * @since 1.3
421      */

422     public StyleSheet JavaDoc[] getStyleSheets() {
423     StyleSheet JavaDoc[] retValue;
424
425     synchronized(this) {
426         if (linkedStyleSheets != null) {
427         retValue = new StyleSheet JavaDoc[linkedStyleSheets.size()];
428         linkedStyleSheets.copyInto(retValue);
429         }
430         else {
431         retValue = null;
432         }
433     }
434     return retValue;
435     }
436
437     /**
438      * Imports a style sheet from <code>url</code>. The resulting rules
439      * are directly added to the receiver. If you do not want the rules
440      * to become part of the receiver, create a new StyleSheet and use
441      * addStyleSheet to link it in.
442      *
443      * @since 1.3
444      */

445     public void importStyleSheet(URL url) {
446     try {
447         InputStream is;
448
449         is = url.openStream();
450         Reader r = new BufferedReader(new InputStreamReader(is));
451         CssParser parser = new CssParser();
452         parser.parse(url, r, false, true);
453         r.close();
454         is.close();
455     } catch (Throwable JavaDoc e) {
456         // on error we simply have no styles... the html
457
// will look mighty wrong but still function.
458
}
459     }
460
461     /**
462      * Sets the base. All import statements that are relative, will be
463      * relative to <code>base</code>.
464      *
465      * @since 1.3
466      */

467     public void setBase(URL base) {
468     this.base = base;
469     }
470
471     /**
472      * Returns the base.
473      *
474      * @since 1.3
475      */

476     public URL getBase() {
477     return base;
478     }
479
480     /**
481      * Adds a CSS attribute to the given set.
482      *
483      * @since 1.3
484      */

485     public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute JavaDoc key,
486                 String JavaDoc value) {
487     css.addInternalCSSValue(attr, key, value);
488     }
489
490     /**
491      * Adds a CSS attribute to the given set.
492      *
493      * @since 1.3
494      */

495     public boolean addCSSAttributeFromHTML(MutableAttributeSet attr,
496                        CSS.Attribute JavaDoc key, String JavaDoc value) {
497     Object JavaDoc iValue = css.getCssValue(key, value);
498     if (iValue != null) {
499         attr.addAttribute(key, iValue);
500         return true;
501     }
502     return false;
503     }
504
505     // ---- Conversion functionality ---------------------------------
506

507     /**
508      * Converts a set of HTML attributes to an equivalent
509      * set of CSS attributes.
510      *
511      * @param htmlAttrSet AttributeSet containing the HTML attributes.
512      */

513     public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
514     AttributeSet cssAttrSet = css.translateHTMLToCSS(htmlAttrSet);
515
516     MutableAttributeSet cssStyleSet = addStyle(null, null);
517     cssStyleSet.addAttributes(cssAttrSet);
518
519     return cssStyleSet;
520     }
521
522     /**
523      * Adds an attribute to the given set, and returns
524      * the new representative set. This is reimplemented to
525      * convert StyleConstant attributes to CSS prior to forwarding
526      * to the superclass behavior. The StyleConstants attribute
527      * has no corresponding CSS entry, the StyleConstants attribute
528      * is stored (but will likely be unused).
529      *
530      * @param old the old attribute set
531      * @param key the non-null attribute key
532      * @param value the attribute value
533      * @return the updated attribute set
534      * @see MutableAttributeSet#addAttribute
535      */

536     public AttributeSet addAttribute(AttributeSet old, Object JavaDoc key,
537                      Object JavaDoc value) {
538     if (css == null) {
539         // supers constructor will call this before returning,
540
// and we need to make sure CSS is non null.
541
css = new CSS JavaDoc();
542     }
543     if (key instanceof StyleConstants) {
544             HTML.Tag JavaDoc tag = HTML.getTagForStyleConstantsKey(
545                                 (StyleConstants)key);
546
547             if (tag != null && old.isDefined(tag)) {
548                 old = removeAttribute(old, tag);
549             }
550
551         Object JavaDoc cssValue = css.styleConstantsValueToCSSValue
552                       ((StyleConstants)key, value);
553         if (cssValue != null) {
554         Object JavaDoc cssKey = css.styleConstantsKeyToCSSKey
555                             ((StyleConstants)key);
556         if (cssKey != null) {
557             return super.addAttribute(old, cssKey, cssValue);
558         }
559         }
560     }
561     return super.addAttribute(old, key, value);
562     }
563
564     /**
565      * Adds a set of attributes to the element. If any of these attributes
566      * are StyleConstants attributes, they will be converted to CSS prior
567      * to forwarding to the superclass behavior.
568      *
569      * @param old the old attribute set
570      * @param attr the attributes to add
571      * @return the updated attribute set
572      * @see MutableAttributeSet#addAttribute
573      */

574     public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) {
575         if (!(attr instanceof HTMLDocument.TaggedAttributeSet JavaDoc)) {
576             old = removeHTMLTags(old, attr);
577         }
578     return super.addAttributes(old, convertAttributeSet(attr));
579     }
580
581     /**
582      * Removes an attribute from the set. If the attribute is a StyleConstants
583      * attribute, the request will be converted to a CSS attribute prior to
584      * forwarding to the superclass behavior.
585      *
586      * @param old the old set of attributes
587      * @param key the non-null attribute name
588      * @return the updated attribute set
589      * @see MutableAttributeSet#removeAttribute
590      */

591     public AttributeSet removeAttribute(AttributeSet old, Object JavaDoc key) {
592     if (key instanceof StyleConstants) {
593             HTML.Tag JavaDoc tag = HTML.getTagForStyleConstantsKey(
594                                    (StyleConstants)key);
595             if (tag != null) {
596                 old = super.removeAttribute(old, tag);
597             }
598
599         Object JavaDoc cssKey = css.styleConstantsKeyToCSSKey((StyleConstants)key);
600         if (cssKey != null) {
601         return super.removeAttribute(old, cssKey);
602         }
603     }
604         return super.removeAttribute(old, key);
605     }
606
607     /**
608      * Removes a set of attributes for the element. If any of the attributes
609      * is a StyleConstants attribute, the request will be converted to a CSS
610      * attribute prior to forwarding to the superclass behavior.
611      *
612      * @param old the old attribute set
613      * @param names the attribute names
614      * @return the updated attribute set
615      * @see MutableAttributeSet#removeAttributes
616      */

617     public AttributeSet removeAttributes(AttributeSet old, Enumeration<?> names) {
618         // PENDING: Should really be doing something similar to
619
// removeHTMLTags here, but it is rather expensive to have to
620
// clone names
621
return super.removeAttributes(old, names);
622     }
623
624     /**
625      * Removes a set of attributes. If any of the attributes
626      * is a StyleConstants attribute, the request will be converted to a CSS
627      * attribute prior to forwarding to the superclass behavior.
628      *
629      * @param old the old attribute set
630      * @param attrs the attributes
631      * @return the updated attribute set
632      * @see MutableAttributeSet#removeAttributes
633      */

634     public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) {
635         if (old != attrs) {
636             old = removeHTMLTags(old, attrs);
637         }
638     return super.removeAttributes(old, convertAttributeSet(attrs));
639     }
640
641     /**
642      * Creates a compact set of attributes that might be shared.
643      * This is a hook for subclasses that want to alter the
644      * behavior of SmallAttributeSet. This can be reimplemented
645      * to return an AttributeSet that provides some sort of
646      * attribute conversion.
647      *
648      * @param a The set of attributes to be represented in the
649      * the compact form.
650      */

651     protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) {
652     return new SmallConversionSet(a);
653     }
654
655     /**
656      * Creates a large set of attributes that should trade off
657      * space for time. This set will not be shared. This is
658      * a hook for subclasses that want to alter the behavior
659      * of the larger attribute storage format (which is
660      * SimpleAttributeSet by default). This can be reimplemented
661      * to return a MutableAttributeSet that provides some sort of
662      * attribute conversion.
663      *
664      * @param a The set of attributes to be represented in the
665      * the larger form.
666      */

667     protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) {
668         return new LargeConversionSet(a);
669     }
670
671     /**
672      * For any StyleConstants key in attr that has an associated HTML.Tag,
673      * it is removed from old. The resulting AttributeSet is then returned.
674      */

675     private AttributeSet removeHTMLTags(AttributeSet old, AttributeSet attr) {
676         if (!(attr instanceof LargeConversionSet) &&
677             !(attr instanceof SmallConversionSet)) {
678             Enumeration names = attr.getAttributeNames();
679
680             while (names.hasMoreElements()) {
681                 Object JavaDoc key = names.nextElement();
682
683                 if (key instanceof StyleConstants) {
684                     HTML.Tag JavaDoc tag = HTML.getTagForStyleConstantsKey(
685                         (StyleConstants)key);
686
687                     if (tag != null && old.isDefined(tag)) {
688                         old = super.removeAttribute(old, tag);
689                     }
690                 }
691             }
692         }
693     return old;
694     }
695
696     /**
697      * Converts a set of attributes (if necessary) so that
698      * any attributes that were specified as StyleConstants
699      * attributes and have a CSS mapping, will be converted
700      * to CSS attributes.
701      */

702     AttributeSet convertAttributeSet(AttributeSet a) {
703     if ((a instanceof LargeConversionSet) ||
704         (a instanceof SmallConversionSet)) {
705         // known to be converted.
706
return a;
707     }
708     // in most cases, there are no StyleConstants attributes
709
// so we iterate the collection of keys to avoid creating
710
// a new set.
711
Enumeration names = a.getAttributeNames();
712     while (names.hasMoreElements()) {
713         Object JavaDoc name = names.nextElement();
714         if (name instanceof StyleConstants) {
715         // we really need to do a conversion, iterate again
716
// building a new set.
717
MutableAttributeSet converted = new LargeConversionSet();
718         Enumeration keys = a.getAttributeNames();
719         while (keys.hasMoreElements()) {
720             Object JavaDoc key = keys.nextElement();
721             Object JavaDoc cssValue = null;
722             if (key instanceof StyleConstants) {
723             // convert the StyleConstants attribute if possible
724
Object JavaDoc cssKey = css.styleConstantsKeyToCSSKey
725                                 ((StyleConstants)key);
726             if (cssKey != null) {
727                 Object JavaDoc value = a.getAttribute(key);
728                 cssValue = css.styleConstantsValueToCSSValue
729                            ((StyleConstants)key, value);
730                 if (cssValue != null) {
731                 converted.addAttribute(cssKey, cssValue);
732                 }
733             }
734             }
735             if (cssValue == null) {
736             converted.addAttribute(key, a.getAttribute(key));
737             }
738         }
739         return converted;
740             }
741     }
742     return a;
743     }
744
745     /**
746      * Large set of attributes that does conversion of requests
747      * for attributes of type StyleConstants.
748      */

749     class LargeConversionSet extends SimpleAttributeSet {
750
751     /**
752      * Creates a new attribute set based on a supplied set of attributes.
753      *
754      * @param source the set of attributes
755      */

756         public LargeConversionSet(AttributeSet source) {
757         super(source);
758     }
759
760         public LargeConversionSet() {
761         super();
762     }
763
764         /**
765          * Checks whether a given attribute is defined.
766          *
767          * @param key the attribute key
768          * @return true if the attribute is defined
769          * @see AttributeSet#isDefined
770          */

771         public boolean isDefined(Object JavaDoc key) {
772         if (key instanceof StyleConstants) {
773         Object JavaDoc cssKey = css.styleConstantsKeyToCSSKey
774                             ((StyleConstants)key);
775         if (cssKey != null) {
776             return super.isDefined(cssKey);
777         }
778         }
779         return super.isDefined(key);
780     }
781
782         /**
783          * Gets the value of an attribute.
784          *
785          * @param key the attribute name
786          * @return the attribute value
787          * @see AttributeSet#getAttribute
788          */

789         public Object JavaDoc getAttribute(Object JavaDoc key) {
790         if (key instanceof StyleConstants) {
791         Object JavaDoc cssKey = css.styleConstantsKeyToCSSKey
792                             ((StyleConstants)key);
793         if (cssKey != null) {
794             Object JavaDoc value = super.getAttribute(cssKey);
795             if (value != null) {
796             return css.cssValueToStyleConstantsValue
797                                ((StyleConstants)key, value);
798             }
799         }
800         }
801         return super.getAttribute(key);
802     }
803     }
804
805     /**
806      * Small set of attributes that does conversion of requests
807      * for attributes of type StyleConstants.
808      */

809     class SmallConversionSet extends SmallAttributeSet {
810
811     /**
812      * Creates a new attribute set based on a supplied set of attributes.
813      *
814      * @param source the set of attributes
815      */

816         public SmallConversionSet(AttributeSet attrs) {
817         super(attrs);
818     }
819     
820         /**
821          * Checks whether a given attribute is defined.
822          *
823          * @param key the attribute key
824          * @return true if the attribute is defined
825          * @see AttributeSet#isDefined
826          */

827         public boolean isDefined(Object JavaDoc key) {
828         if (key instanceof StyleConstants) {
829         Object JavaDoc cssKey = css.styleConstantsKeyToCSSKey
830                             ((StyleConstants)key);
831         if (cssKey != null) {
832             return super.isDefined(cssKey);
833         }
834         }
835         return super.isDefined(key);
836     }
837
838         /**
839          * Gets the value of an attribute.
840          *
841          * @param key the attribute name
842          * @return the attribute value
843          * @see AttributeSet#getAttribute
844          */

845         public Object JavaDoc getAttribute(Object JavaDoc key) {
846         if (key instanceof StyleConstants) {
847         Object JavaDoc cssKey = css.styleConstantsKeyToCSSKey
848                             ((StyleConstants)key);
849         if (cssKey != null) {
850             Object JavaDoc value = super.getAttribute(cssKey);
851             if (value != null) {
852             return css.cssValueToStyleConstantsValue
853                                ((StyleConstants)key, value);
854             }
855         }
856         }
857         return super.getAttribute(key);
858     }
859     }
860
861     // ---- Resource handling ----------------------------------------
862

863     /**
864      * Fetches the font to use for the given set of attributes.
865      */

866     public Font getFont(AttributeSet a) {
867     return css.getFont(this, a, 12, this);
868     }
869
870     /**
871      * Takes a set of attributes and turn it into a foreground color
872      * specification. This might be used to specify things
873      * like brighter, more hue, etc.
874      *
875      * @param a the set of attributes
876      * @return the color
877      */

878     public Color getForeground(AttributeSet a) {
879     Color c = css.getColor(a, CSS.Attribute.COLOR);
880     if (c == null) {
881         return Color.black;
882     }
883     return c;
884     }
885
886     /**
887      * Takes a set of attributes and turn it into a background color
888      * specification. This might be used to specify things
889      * like brighter, more hue, etc.
890      *
891      * @param a the set of attributes
892      * @return the color
893      */

894     public Color getBackground(AttributeSet a) {
895     return css.getColor(a, CSS.Attribute.BACKGROUND_COLOR);
896     }
897
898     /**
899      * Fetches the box formatter to use for the given set
900      * of CSS attributes.
901      */

902     public BoxPainter getBoxPainter(AttributeSet a) {
903     return new BoxPainter(a, css, this);
904     }
905
906     /**
907      * Fetches the list formatter to use for the given set
908      * of CSS attributes.
909      */

910     public ListPainter getListPainter(AttributeSet a) {
911     return new ListPainter(a, this);
912     }
913
914     /**
915      * Sets the base font size, with valid values between 1 and 7.
916      */

917     public void setBaseFontSize(int sz) {
918     css.setBaseFontSize(sz);
919     }
920
921     /**
922      * Sets the base font size from the passed in String. The string
923      * can either identify a specific font size, with legal values between
924      * 1 and 7, or identifiy a relative font size such as +1 or -2.
925      */

926     public void setBaseFontSize(String JavaDoc size) {
927     css.setBaseFontSize(size);
928     }
929
930     public static int getIndexOfSize(float pt) {
931     return CSS.getIndexOfSize(pt, sizeMapDefault);
932     }
933
934     /**
935      * Returns the point size, given a size index.
936      */

937     public float getPointSize(int index) {
938     return css.getPointSize(index, this);
939     }
940
941     /**
942      * Given a string such as "+2", "-2", or "2",
943      * returns a point size value.
944      */

945     public float getPointSize(String JavaDoc size) {
946     return css.getPointSize(size, this);
947     }
948
949     /**
950      * Converts a color string such as "RED" or "#NNNNNN" to a Color.
951      * Note: This will only convert the HTML3.2 color strings
952      * or a string of length 7;
953      * otherwise, it will return null.
954      */

955     public Color stringToColor(String JavaDoc string) {
956     return CSS.stringToColor(string);
957     }
958
959     /**
960      * Returns the ImageIcon to draw in the background for
961      * <code>attr</code>.
962      */

963     ImageIcon JavaDoc getBackgroundImage(AttributeSet attr) {
964     Object JavaDoc value = attr.getAttribute(CSS.Attribute.BACKGROUND_IMAGE);
965
966     if (value != null) {
967         return ((CSS.BackgroundImage JavaDoc)value).getImage(getBase());
968     }
969     return null;
970     }
971
972     /**
973      * Adds a rule into the StyleSheet.
974      *
975      * @param selector the selector to use for the rule.
976      * This will be a set of simple selectors, and must
977      * be a length of 1 or greater.
978      * @param declaration the set of CSS attributes that
979      * make up the rule.
980      */

981     void addRule(String JavaDoc[] selector, AttributeSet declaration,
982          boolean isLinked) {
983     int n = selector.length;
984     StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
985     sb.append(selector[0]);
986     for (int counter = 1; counter < n; counter++) {
987         sb.append(' ');
988         sb.append(selector[counter]);
989     }
990     String JavaDoc selectorName = sb.toString();
991     Style rule = getStyle(selectorName);
992     if (rule == null) {
993         // Notice how the rule is first created, and it not part of
994
// the synchronized block. It is done like this as creating
995
// a new rule will fire a ChangeEvent. We do not want to be
996
// holding the lock when calling to other objects, it can
997
// result in deadlock.
998
Style altRule = addStyle(selectorName, null);
999         synchronized(this) {
1000        SelectorMapping mapping = getRootSelectorMapping();
1001        for (int i = n - 1; i >= 0; i--) {
1002            mapping = mapping.getChildSelectorMapping
1003                                      (selector[i], true);
1004        }
1005        rule = mapping.getStyle();
1006        if (rule == null) {
1007                    rule = altRule;
1008                    mapping.setStyle(rule);
1009            refreshResolvedRules(selectorName, selector, rule,
1010                     mapping.getSpecificity());
1011        }
1012        }
1013    }
1014    if (isLinked) {
1015        rule = getLinkedStyle(rule);
1016    }
1017    rule.addAttributes(declaration);
1018    }
1019
1020    //
1021
// The following gaggle of methods is used in maintaing the rules from
1022
// the sheet.
1023
//
1024

1025    /**
1026     * Updates the attributes of the rules to reference any related
1027     * rules in <code>ss</code>.
1028     */

1029    private synchronized void linkStyleSheetAt(StyleSheet JavaDoc ss, int index) {
1030    if (resolvedStyles.size() > 0) {
1031        Enumeration values = resolvedStyles.elements();
1032        while (values.hasMoreElements()) {
1033        ResolvedStyle rule = (ResolvedStyle)values.nextElement();
1034        rule.insertExtendedStyleAt(ss.getRule(rule.getName()),
1035                       index);
1036        }
1037    }
1038    }
1039
1040    /**
1041     * Removes references to the rules in <code>ss</code>.
1042     * <code>index</code> gives the index the StyleSheet was at, that is
1043     * how many StyleSheets had been added before it.
1044     */

1045    private synchronized void unlinkStyleSheet(StyleSheet JavaDoc ss, int index) {
1046    if (resolvedStyles.size() > 0) {
1047        Enumeration values = resolvedStyles.elements();
1048        while (values.hasMoreElements()) {
1049        ResolvedStyle rule = (ResolvedStyle)values.nextElement();
1050        rule.removeExtendedStyleAt(index);
1051        }
1052    }
1053    }
1054
1055    /**
1056     * Returns the simple selectors that comprise selector.
1057     */

1058    /* protected */
1059    String JavaDoc[] getSimpleSelectors(String JavaDoc selector) {
1060    selector = cleanSelectorString(selector);
1061    SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1062    Vector selectors = sb.getVector();
1063    int lastIndex = 0;
1064    int length = selector.length();
1065    while (lastIndex != -1) {
1066        int newIndex = selector.indexOf(' ', lastIndex);
1067        if (newIndex != -1) {
1068        selectors.addElement(selector.substring(lastIndex, newIndex));
1069        if (++newIndex == length) {
1070            lastIndex = -1;
1071        }
1072        else {
1073            lastIndex = newIndex;
1074        }
1075        }
1076        else {
1077        selectors.addElement(selector.substring(lastIndex));
1078        lastIndex = -1;
1079        }
1080    }
1081    String JavaDoc[] retValue = new String JavaDoc[selectors.size()];
1082    selectors.copyInto(retValue);
1083    SearchBuffer.releaseSearchBuffer(sb);
1084    return retValue;
1085    }
1086
1087    /**
1088     * Returns a string that only has one space between simple selectors,
1089     * which may be the passed in String.
1090     */

1091    /*protected*/ String JavaDoc cleanSelectorString(String JavaDoc selector) {
1092    boolean lastWasSpace = true;
1093    for (int counter = 0, maxCounter = selector.length();
1094         counter < maxCounter; counter++) {
1095        switch(selector.charAt(counter)) {
1096        case ' ':
1097        if (lastWasSpace) {
1098            return _cleanSelectorString(selector);
1099        }
1100        lastWasSpace = true;
1101        break;
1102        case '\n':
1103        case '\r':
1104        case '\t':
1105        return _cleanSelectorString(selector);
1106        default:
1107        lastWasSpace = false;
1108        }
1109    }
1110    if (lastWasSpace) {
1111        return _cleanSelectorString(selector);
1112    }
1113    // It was fine.
1114
return selector;
1115    }
1116
1117    /**
1118     * Returns a new String that contains only one space between non
1119     * white space characters.
1120     */

1121    private String JavaDoc _cleanSelectorString(String JavaDoc selector) {
1122    SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1123    StringBuffer JavaDoc buff = sb.getStringBuffer();
1124    boolean lastWasSpace = true;
1125    int lastIndex = 0;
1126    char[] chars = selector.toCharArray();
1127    int numChars = chars.length;
1128    String JavaDoc retValue = null;
1129    try {
1130        for (int counter = 0; counter < numChars; counter++) {
1131        switch(chars[counter]) {
1132        case ' ':
1133            if (!lastWasSpace) {
1134            lastWasSpace = true;
1135            if (lastIndex < counter) {
1136                buff.append(chars, lastIndex,
1137                    1 + counter - lastIndex);
1138            }
1139            }
1140            lastIndex = counter + 1;
1141            break;
1142        case '\n':
1143        case '\r':
1144        case '\t':
1145            if (!lastWasSpace) {
1146            lastWasSpace = true;
1147            if (lastIndex < counter) {
1148                buff.append(chars, lastIndex,
1149                    counter - lastIndex);
1150                buff.append(' ');
1151            }
1152            }
1153            lastIndex = counter + 1;
1154            break;
1155        default:
1156            lastWasSpace = false;
1157            break;
1158        }
1159        }
1160        if (lastWasSpace && buff.length() > 0) {
1161        // Remove last space.
1162
buff.setLength(buff.length() - 1);
1163        }
1164        else if (lastIndex < numChars) {
1165        buff.append(chars, lastIndex, numChars - lastIndex);
1166        }
1167        retValue = buff.toString();
1168    }
1169    finally {
1170        SearchBuffer.releaseSearchBuffer(sb);
1171    }
1172    return retValue;
1173    }
1174
1175    /**
1176     * Returns the root selector mapping that all selectors are relative
1177     * to. This is an inverted graph of the selectors.
1178     */

1179    private SelectorMapping getRootSelectorMapping() {
1180    return selectorMapping;
1181    }
1182
1183    /**
1184     * Returns the specificity of the passed in String. It assumes the
1185     * passed in string doesn't contain junk, that is each selector is
1186     * separated by a space and each selector at most contains one . or one
1187     * #. A simple selector has a weight of 1, an id selector has a weight
1188     * of 100, and a class selector has a weight of 10000.
1189     */

1190    /*protected*/ static int getSpecificity(String JavaDoc selector) {
1191        int specificity = 0;
1192        boolean lastWasSpace = true;
1193
1194        for (int counter = 0, maxCounter = selector.length();
1195             counter < maxCounter; counter++) {
1196            switch(selector.charAt(counter)) {
1197            case '.':
1198                specificity += 100;
1199                break;
1200            case '#':
1201                specificity += 10000;
1202                break;
1203            case ' ':
1204                lastWasSpace = true;
1205                break;
1206            default:
1207                if (lastWasSpace) {
1208                    lastWasSpace = false;
1209                    specificity += 1;
1210                }
1211            }
1212        }
1213        return specificity;
1214    }
1215
1216    /**
1217     * Returns the style that linked attributes should be added to. This
1218     * will create the style if necessary.
1219     */

1220    private Style getLinkedStyle(Style localStyle) {
1221    // NOTE: This is not synchronized, and the caller of this does
1222
// not synchronize. There is the chance for one of the callers to
1223
// overwrite the existing resolved parent, but it is quite rare.
1224
// The reason this is left like this is because setResolveParent
1225
// will fire a ChangeEvent. It is really, REALLY bad for us to
1226
// hold a lock when calling outside of us, it may cause a deadlock.
1227
Style retStyle = (Style)localStyle.getResolveParent();
1228    if (retStyle == null) {
1229        retStyle = addStyle(null, null);
1230        localStyle.setResolveParent(retStyle);
1231    }
1232    return retStyle;
1233    }
1234
1235    /**
1236     * Returns the resolved style for <code>selector</code>. This will
1237     * create the resolved style, if necessary.
1238     */

1239    private synchronized Style getResolvedStyle(String JavaDoc selector,
1240                        Vector elements,
1241                        HTML.Tag JavaDoc t) {
1242    Style retStyle = (Style)resolvedStyles.get(selector);
1243    if (retStyle == null) {
1244        retStyle = createResolvedStyle(selector, elements, t);
1245    }
1246    return retStyle;
1247    }
1248
1249    /**
1250     * Returns the resolved style for <code>selector</code>. This will
1251     * create the resolved style, if necessary.
1252     */

1253    private synchronized Style getResolvedStyle(String JavaDoc selector) {
1254    Style retStyle = (Style)resolvedStyles.get(selector);
1255    if (retStyle == null) {
1256        retStyle = createResolvedStyle(selector);
1257    }
1258    return retStyle;
1259    }
1260
1261    /**
1262     * Adds <code>mapping</code> to <code>elements</code>. It is added
1263     * such that <code>elements</code> will remain ordered by
1264     * specificity.
1265     */

1266    private void addSortedStyle(SelectorMapping mapping, Vector elements) {
1267    int size = elements.size();
1268
1269    if (size > 0) {
1270        int specificity = mapping.getSpecificity();
1271
1272        for (int counter = 0; counter < size; counter++) {
1273        if (specificity >= ((SelectorMapping)elements.elementAt
1274                                    (counter)).getSpecificity()) {
1275            elements.insertElementAt(mapping, counter);
1276            return;
1277        }
1278        }
1279    }
1280    elements.addElement(mapping);
1281    }
1282
1283    /**
1284     * Adds <code>parentMapping</code> to <code>styles</code>, and
1285     * recursively calls this method if <code>parentMapping</code> has
1286     * any child mappings for any of the Elements in <code>elements</code>.
1287     */

1288    private synchronized void getStyles(SelectorMapping parentMapping,
1289                           Vector styles,
1290                           String JavaDoc[] tags, String JavaDoc[] ids, String JavaDoc[] classes,
1291                           int index, int numElements,
1292                           Hashtable alreadyChecked) {
1293    // Avoid desending the same mapping twice.
1294
if (alreadyChecked.contains(parentMapping)) {
1295        return;
1296    }
1297    alreadyChecked.put(parentMapping, parentMapping);
1298    Style style = parentMapping.getStyle();
1299    if (style != null) {
1300        addSortedStyle(parentMapping, styles);
1301    }
1302    for (int counter = index; counter < numElements; counter++) {
1303            String JavaDoc tagString = tags[counter];
1304            if (tagString != null) {
1305        SelectorMapping childMapping = parentMapping.
1306                                getChildSelectorMapping(tagString, false);
1307        if (childMapping != null) {
1308            getStyles(childMapping, styles, tags, ids, classes,
1309                              counter + 1, numElements, alreadyChecked);
1310        }
1311        if (classes[counter] != null) {
1312            String JavaDoc className = classes[counter];
1313            childMapping = parentMapping.getChildSelectorMapping(
1314                                         tagString + "." + className, false);
1315            if (childMapping != null) {
1316            getStyles(childMapping, styles, tags, ids, classes,
1317                                  counter + 1, numElements, alreadyChecked);
1318            }
1319            childMapping = parentMapping.getChildSelectorMapping(
1320                                         "." + className, false);
1321            if (childMapping != null) {
1322            getStyles(childMapping, styles, tags, ids, classes,
1323                                  counter + 1, numElements, alreadyChecked);
1324            }
1325        }
1326        if (ids[counter] != null) {
1327            String JavaDoc idName = ids[counter];
1328            childMapping = parentMapping.getChildSelectorMapping(
1329                                         tagString + "#" + idName, false);
1330            if (childMapping != null) {
1331            getStyles(childMapping, styles, tags, ids, classes,
1332                                  counter + 1, numElements, alreadyChecked);
1333            }
1334            childMapping = parentMapping.getChildSelectorMapping(
1335                                   "#" + idName, false);
1336            if (childMapping != null) {
1337            getStyles(childMapping, styles, tags, ids, classes,
1338                                  counter + 1, numElements, alreadyChecked);
1339            }
1340        }
1341        }
1342    }
1343    }
1344
1345    /**
1346     * Creates and returns a Style containing all the rules that match
1347     * <code>selector</code>.
1348     */

1349    private synchronized Style createResolvedStyle(String JavaDoc selector,
1350                      String JavaDoc[] tags,
1351                      String JavaDoc[] ids, String JavaDoc[] classes) {
1352    SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1353    Vector tempVector = sb.getVector();
1354    Hashtable tempHashtable = sb.getHashtable();
1355    // Determine all the Styles that are appropriate, placing them
1356
// in tempVector
1357
try {
1358        SelectorMapping mapping = getRootSelectorMapping();
1359        int numElements = tags.length;
1360        String JavaDoc tagString = tags[0];
1361        SelectorMapping childMapping = mapping.getChildSelectorMapping(
1362                                                   tagString, false);
1363        if (childMapping != null) {
1364        getStyles(childMapping, tempVector, tags, ids, classes, 1,
1365              numElements, tempHashtable);
1366        }
1367        if (classes[0] != null) {
1368        String JavaDoc className = classes[0];
1369        childMapping = mapping.getChildSelectorMapping(
1370                                       tagString + "." + className, false);
1371        if (childMapping != null) {
1372            getStyles(childMapping, tempVector, tags, ids, classes, 1,
1373                  numElements, tempHashtable);
1374        }
1375        childMapping = mapping.getChildSelectorMapping(
1376                                       "." + className, false);
1377        if (childMapping != null) {
1378            getStyles(childMapping, tempVector, tags, ids, classes,
1379                  1, numElements, tempHashtable);
1380        }
1381        }
1382        if (ids[0] != null) {
1383        String JavaDoc idName = ids[0];
1384        childMapping = mapping.getChildSelectorMapping(
1385                                       tagString + "#" + idName, false);
1386        if (childMapping != null) {
1387            getStyles(childMapping, tempVector, tags, ids, classes,
1388                  1, numElements, tempHashtable);
1389        }
1390        childMapping = mapping.getChildSelectorMapping(
1391                                       "#" + idName, false);
1392        if (childMapping != null) {
1393            getStyles(childMapping, tempVector, tags, ids, classes,
1394                  1, numElements, tempHashtable);
1395        }
1396        }
1397        // Create a new Style that will delegate to all the matching
1398
// Styles.
1399
int numLinkedSS = (linkedStyleSheets != null) ?
1400                      linkedStyleSheets.size() : 0;
1401        int numStyles = tempVector.size();
1402        AttributeSet[] attrs = new AttributeSet[numStyles + numLinkedSS];
1403        for (int counter = 0; counter < numStyles; counter++) {
1404        attrs[counter] = ((SelectorMapping)tempVector.
1405                                  elementAt(counter)).getStyle();
1406        }
1407        // Get the AttributeSet from linked style sheets.
1408
for (int counter = 0; counter < numLinkedSS; counter++) {
1409        AttributeSet attr = ((StyleSheet JavaDoc)linkedStyleSheets.
1410                 elementAt(counter)).getRule(selector);
1411        if (attr == null) {
1412            attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
1413        }
1414        else {
1415            attrs[counter + numStyles] = attr;
1416        }
1417        }
1418        ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
1419                               numStyles);
1420        resolvedStyles.put(selector, retStyle);
1421        return retStyle;
1422    }
1423    finally {
1424        SearchBuffer.releaseSearchBuffer(sb);
1425    }
1426    }
1427
1428    /**
1429     * Creates and returns a Style containing all the rules that
1430     * matches <code>selector</code>.
1431     *
1432     * @param elements a Vector of all the Elements
1433     * the style is being asked for. The
1434     * first Element is the deepest Element, with the last Element
1435     * representing the root.
1436     * @param t the Tag to use for
1437     * the first Element in <code>elements</code>
1438     */

1439    private Style createResolvedStyle(String JavaDoc selector, Vector elements,
1440                      HTML.Tag JavaDoc t) {
1441    int numElements = elements.size();
1442        // Build three arrays, one for tags, one for class's, and one for
1443
// id's
1444
String JavaDoc tags[] = new String JavaDoc[numElements];
1445        String JavaDoc ids[] = new String JavaDoc[numElements];
1446        String JavaDoc classes[] = new String JavaDoc[numElements];
1447        for (int counter = 0; counter < numElements; counter++) {
1448            Element e = (Element)elements.elementAt(counter);
1449            AttributeSet attr = e.getAttributes();
1450        if (counter == 0 && e.isLeaf()) {
1451        // For leafs, we use the second tier attributes.
1452
Object JavaDoc testAttr = attr.getAttribute(t);
1453        if (testAttr instanceof AttributeSet) {
1454            attr = (AttributeSet)testAttr;
1455        }
1456        else {
1457            attr = null;
1458        }
1459        }
1460            if (attr != null) {
1461        HTML.Tag JavaDoc tag = (HTML.Tag JavaDoc)attr.getAttribute(StyleConstants.
1462                               NameAttribute);
1463                if (tag != null) {
1464                    tags[counter] = tag.toString();
1465                }
1466                else {
1467                    tags[counter] = null;
1468                }
1469        if (attr.isDefined(HTML.Attribute.CLASS)) {
1470                    classes[counter] = attr.getAttribute
1471                          (HTML.Attribute.CLASS).toString();
1472        }
1473                else {
1474                    classes[counter] = null;
1475                }
1476        if (attr.isDefined(HTML.Attribute.ID)) {
1477                    ids[counter] = attr.getAttribute(HTML.Attribute.ID).
1478                            toString();
1479                }
1480                else {
1481                    ids[counter] = null;
1482                }
1483            }
1484            else {
1485                tags[counter] = ids[counter] = classes[counter] = null;
1486            }
1487        }
1488        tags[0] = t.toString();
1489    return createResolvedStyle(selector, tags, ids, classes);
1490    }
1491
1492    /**
1493     * Creates and returns a Style containing all the rules that match
1494     * <code>selector</code>. It is assumed that each simple selector
1495     * in <code>selector</code> is separated by a space.
1496     */

1497    private Style createResolvedStyle(String JavaDoc selector) {
1498    SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1499    // Will contain the tags, ids, and classes, in that order.
1500
Vector elements = sb.getVector();
1501    try {
1502        boolean done;
1503        int dotIndex = 0;
1504        int spaceIndex = 0;
1505        int poundIndex = 0;
1506        int lastIndex = 0;
1507        int length = selector.length();
1508        while (lastIndex < length) {
1509        if (dotIndex == lastIndex) {
1510            dotIndex = selector.indexOf('.', lastIndex);
1511        }
1512        if (poundIndex == lastIndex) {
1513            poundIndex = selector.indexOf('#', lastIndex);
1514        }
1515        spaceIndex = selector.indexOf(' ', lastIndex);
1516        if (spaceIndex == -1) {
1517            spaceIndex = length;
1518        }
1519        if (dotIndex != -1 && poundIndex != -1 &&
1520            dotIndex < spaceIndex && poundIndex < spaceIndex) {
1521            if (poundIndex < dotIndex) {
1522            // #.
1523
if (lastIndex == poundIndex) {
1524                elements.addElement("");
1525            }
1526            else {
1527                elements.addElement(selector.substring(lastIndex,
1528                                  poundIndex));
1529            }
1530            if ((dotIndex + 1) < spaceIndex) {
1531                elements.addElement(selector.substring
1532                        (dotIndex + 1, spaceIndex));
1533            }
1534            else {
1535                elements.addElement(null);
1536            }
1537            if ((poundIndex + 1) == dotIndex) {
1538                elements.addElement(null);
1539            }
1540            else {
1541                elements.addElement(selector.substring
1542                        (poundIndex + 1, dotIndex));
1543            }
1544            }
1545            else if(poundIndex < spaceIndex) {
1546            // .#
1547
if (lastIndex == dotIndex) {
1548                elements.addElement("");
1549            }
1550            else {
1551                elements.addElement(selector.substring(lastIndex,
1552                                  dotIndex));
1553            }
1554            if ((dotIndex + 1) < poundIndex) {
1555                elements.addElement(selector.substring
1556                        (dotIndex + 1, poundIndex));
1557            }
1558            else {
1559                elements.addElement(null);
1560            }
1561            if ((poundIndex + 1) == spaceIndex) {
1562                elements.addElement(null);
1563            }
1564            else {
1565                elements.addElement(selector.substring
1566                        (poundIndex + 1, spaceIndex));
1567            }
1568            }
1569            dotIndex = poundIndex = spaceIndex + 1;
1570        }
1571        else if (dotIndex != -1 && dotIndex < spaceIndex) {
1572            // .
1573
if (dotIndex == lastIndex) {
1574            elements.addElement("");
1575            }
1576            else {
1577            elements.addElement(selector.substring(lastIndex,
1578                                   dotIndex));
1579            }
1580            if ((dotIndex + 1) == spaceIndex) {
1581            elements.addElement(null);
1582            }
1583            else {
1584            elements.addElement(selector.substring(dotIndex + 1,
1585                                   spaceIndex));
1586            }
1587            elements.addElement(null);
1588            dotIndex = spaceIndex + 1;
1589        }
1590        else if (poundIndex != -1 && poundIndex < spaceIndex) {
1591            // #
1592
if (poundIndex == lastIndex) {
1593            elements.addElement("");
1594            }
1595            else {
1596            elements.addElement(selector.substring(lastIndex,
1597                                   poundIndex));
1598            }
1599            elements.addElement(null);
1600            if ((poundIndex + 1) == spaceIndex) {
1601            elements.addElement(null);
1602            }
1603            else {
1604            elements.addElement(selector.substring(poundIndex + 1,
1605                                   spaceIndex));
1606            }
1607            poundIndex = spaceIndex + 1;
1608        }
1609        else {
1610            // id
1611
elements.addElement(selector.substring(lastIndex,
1612                               spaceIndex));
1613            elements.addElement(null);
1614            elements.addElement(null);
1615        }
1616        lastIndex = spaceIndex + 1;
1617        }
1618        // Create the tag, id, and class arrays.
1619
int total = elements.size();
1620        int numTags = total / 3;
1621        String JavaDoc[] tags = new String JavaDoc[numTags];
1622        String JavaDoc[] ids = new String JavaDoc[numTags];
1623        String JavaDoc[] classes = new String JavaDoc[numTags];
1624        for (int index = 0, eIndex = total - 3; index < numTags;
1625         index++, eIndex -= 3) {
1626        tags[index] = (String JavaDoc)elements.elementAt(eIndex);
1627        classes[index] = (String JavaDoc)elements.elementAt(eIndex + 1);
1628        ids[index] = (String JavaDoc)elements.elementAt(eIndex + 2);
1629        }
1630        return createResolvedStyle(selector, tags, ids, classes);
1631    }
1632    finally {
1633        SearchBuffer.releaseSearchBuffer(sb);
1634    }
1635    }
1636
1637    /**
1638     * Should be invoked when a new rule is added that did not previously
1639     * exist. Goes through and refreshes the necessary resolved
1640     * rules.
1641     */

1642    private synchronized void refreshResolvedRules(String JavaDoc selectorName,
1643                           String JavaDoc[] selector,
1644                           Style newStyle,
1645                           int specificity) {
1646    if (resolvedStyles.size() > 0) {
1647        Enumeration values = resolvedStyles.elements();
1648        while (values.hasMoreElements()) {
1649        ResolvedStyle style = (ResolvedStyle)values.nextElement();
1650        if (style.matches(selectorName)) {
1651            style.insertStyle(newStyle, specificity);
1652        }
1653        }
1654    }
1655    }
1656
1657
1658    /**
1659     * A temporary class used to hold a Vector, a StringBuffer and a
1660     * Hashtable. This is used to avoid allocing a lot of garbage when
1661     * searching for rules. Use the static method obtainSearchBuffer and
1662     * releaseSearchBuffer to get a SearchBuffer, and release it when
1663     * done.
1664     */

1665    private static class SearchBuffer {
1666    /** A stack containing instances of SearchBuffer. Used in getting
1667     * rules. */

1668    static Stack searchBuffers = new Stack();
1669    // A set of temporary variables that can be used in whatever way.
1670
Vector vector = null;
1671        StringBuffer JavaDoc stringBuffer = null;
1672        Hashtable hashtable = null;
1673
1674    /**
1675     * Returns an instance of SearchBuffer. Be sure and issue
1676     * a releaseSearchBuffer when done with it.
1677     */

1678    static SearchBuffer obtainSearchBuffer() {
1679        SearchBuffer sb;
1680        try {
1681        if(!searchBuffers.empty()) {
1682           sb = (SearchBuffer)searchBuffers.pop();
1683        } else {
1684           sb = new SearchBuffer();
1685        }
1686        } catch (EmptyStackException ese) {
1687        sb = new SearchBuffer();
1688        }
1689        return sb;
1690    }
1691
1692    /**
1693     * Adds <code>sb</code> to the stack of SearchBuffers that can
1694     * be used.
1695     */

1696    static void releaseSearchBuffer(SearchBuffer sb) {
1697        sb.empty();
1698        searchBuffers.push(sb);
1699    }
1700
1701    StringBuffer JavaDoc getStringBuffer() {
1702        if (stringBuffer == null) {
1703        stringBuffer = new StringBuffer JavaDoc();
1704        }
1705        return stringBuffer;
1706    }
1707
1708    Vector getVector() {
1709        if (vector == null) {
1710        vector = new Vector();
1711        }
1712        return vector;
1713    }
1714
1715    Hashtable getHashtable() {
1716        if (hashtable == null) {
1717        hashtable = new Hashtable();
1718        }
1719        return hashtable;
1720    }
1721
1722        void empty() {
1723        if (stringBuffer != null) {
1724        stringBuffer.setLength(0);
1725        }
1726            if (vector != null) {
1727                vector.removeAllElements();
1728            }
1729            if (hashtable != null) {
1730                hashtable.clear();
1731            }
1732        }
1733    }
1734
1735
1736    static final Border noBorder = new EmptyBorder(0,0,0,0);
1737
1738    /**
1739     * Class to carry out some of the duties of
1740     * CSS formatting. Implementations of this
1741     * class enable views to present the CSS formatting
1742     * while not knowing anything about how the CSS values
1743     * are being cached.
1744     * <p>
1745     * As a delegate of Views, this object is responsible for
1746     * the insets of a View and making sure the background
1747     * is maintained according to the CSS attributes.
1748     */

1749    public static class BoxPainter implements Serializable {
1750
1751    BoxPainter(AttributeSet a, CSS JavaDoc css, StyleSheet JavaDoc ss) {
1752        this.ss = ss;
1753        this.css = css;
1754        border = getBorder(a);
1755        binsets = border.getBorderInsets(null);
1756        topMargin = getLength(CSS.Attribute.MARGIN_TOP, a);
1757        bottomMargin = getLength(CSS.Attribute.MARGIN_BOTTOM, a);
1758        leftMargin = getLength(CSS.Attribute.MARGIN_LEFT, a);
1759        rightMargin = getLength(CSS.Attribute.MARGIN_RIGHT, a);
1760        bg = ss.getBackground(a);
1761        if (ss.getBackgroundImage(a) != null) {
1762        bgPainter = new BackgroundImagePainter(a, css, ss);
1763        }
1764    }
1765
1766    /**
1767     * Fetches a border to render for the given attributes.
1768     * PENDING(prinz) This is pretty badly hacked at the
1769     * moment.
1770     */

1771    Border getBorder(AttributeSet a) {
1772        Border b = noBorder;
1773        Object JavaDoc o = a.getAttribute(CSS.Attribute.BORDER_STYLE);
1774        if (o != null) {
1775        String JavaDoc bstyle = o.toString();
1776        int bw = (int) getLength(CSS.Attribute.BORDER_TOP_WIDTH, a);
1777        if (bw > 0) {
1778            if (bstyle.equals("inset")) {
1779            Color c = getBorderColor(a);
1780            b = new BevelBorder(BevelBorder.LOWERED, c.brighter(), c.darker());
1781            } else if (bstyle.equals("outset")) {
1782            Color c = getBorderColor(a);
1783            b = new BevelBorder(BevelBorder.RAISED, c.brighter(), c.darker());
1784            } else if (bstyle.equals("solid")) {
1785            Color c = getBorderColor(a);
1786            b = new LineBorder(c);
1787            }
1788        }
1789        }
1790        return b;
1791    }
1792
1793    /**
1794     * Fetches the color to use for borders. This will either be
1795     * the value specified by the border-color attribute (which
1796     * is not inherited), or it will default to the color attribute
1797     * (which is inherited).
1798     */

1799    Color getBorderColor(AttributeSet a) {
1800        Color color = css.getColor(a, CSS.Attribute.BORDER_COLOR);
1801        if (color == null) {
1802        color = css.getColor(a, CSS.Attribute.COLOR);
1803        if (color == null) {
1804            return Color.black;
1805        }
1806        }
1807        return color;
1808    }
1809
1810    /**
1811     * Fetches the inset needed on a given side to
1812     * account for the margin, border, and padding.
1813     *
1814     * @param side The size of the box to fetch the
1815     * inset for. This can be View.TOP,
1816     * View.LEFT, View.BOTTOM, or View.RIGHT.
1817     * @param v the view making the request. This is
1818     * used to get the AttributeSet, and may be used to
1819     * resolve percentage arguments.
1820     * @exception IllegalArgumentException for an invalid direction
1821     */

1822        public float getInset(int side, View v) {
1823        AttributeSet a = v.getAttributes();
1824        float inset = 0;
1825        switch(side) {
1826        case View.LEFT:
1827                inset += getOrientationMargin(HorizontalMargin.LEFT,
1828                                              leftMargin, a, isLeftToRight(v));
1829        inset += binsets.left;
1830        inset += getLength(CSS.Attribute.PADDING_LEFT, a);
1831        break;
1832        case View.RIGHT:
1833                inset += getOrientationMargin(HorizontalMargin.RIGHT,
1834                                              rightMargin, a, isLeftToRight(v));
1835        inset += binsets.right;
1836        inset += getLength(CSS.Attribute.PADDING_RIGHT, a);
1837        break;
1838        case View.TOP:
1839        inset += topMargin;
1840        inset += binsets.top;
1841        inset += getLength(CSS.Attribute.PADDING_TOP, a);
1842        break;
1843        case View.BOTTOM:
1844        inset += bottomMargin;
1845        inset += binsets.bottom;
1846        inset += getLength(CSS.Attribute.PADDING_BOTTOM, a);
1847        break;
1848        default:
1849        throw new IllegalArgumentException JavaDoc("Invalid side: " + side);
1850        }
1851        return inset;
1852    }
1853
1854    /**
1855     * Paints the CSS box according to the attributes
1856     * given. This should paint the border, padding,
1857     * and background.
1858     *
1859     * @param g the rendering surface.
1860     * @param x the x coordinate of the allocated area to
1861     * render into.
1862     * @param y the y coordinate of the allocated area to
1863     * render into.
1864     * @param w the width of the allocated area to render into.
1865     * @param h the height of the allocated area to render into.
1866     * @param v the view making the request. This is
1867     * used to get the AttributeSet, and may be used to
1868     * resolve percentage arguments.
1869     */

1870        public void paint(Graphics g, float x, float y, float w, float h, View v) {
1871        // PENDING(prinz) implement real rendering... which would
1872
// do full set of border and background capabilities.
1873
// remove margin
1874

1875            float dx = 0;
1876            float dy = 0;
1877            float dw = 0;
1878            float dh = 0;
1879            AttributeSet a = v.getAttributes();
1880            boolean isLeftToRight = isLeftToRight(v);
1881            float localLeftMargin = getOrientationMargin(HorizontalMargin.LEFT,
1882                                                         leftMargin,
1883                                                         a, isLeftToRight);
1884            float localRightMargin = getOrientationMargin(HorizontalMargin.RIGHT,
1885                                                          rightMargin,
1886                                                          a, isLeftToRight);
1887            if (!(v instanceof HTMLEditorKit.HTMLFactory.BodyBlockView JavaDoc)) {
1888                dx = localLeftMargin;
1889                dy = topMargin;
1890                dw = -(localLeftMargin + localRightMargin);
1891                dh = -(topMargin + bottomMargin);
1892            }
1893            if (bg != null) {
1894                g.setColor(bg);
1895                g.fillRect((int) (x + dx),
1896                           (int) (y + dy),
1897                           (int) (w + dw),
1898                           (int) (h + dh));
1899            }
1900            if (bgPainter != null) {
1901                bgPainter.paint(g, x + dx, y + dy, w + dw, h + dh, v);
1902            }
1903            x += localLeftMargin;
1904        y += topMargin;
1905            w -= localLeftMargin + localRightMargin;
1906        h -= topMargin + bottomMargin;
1907        border.paintBorder(null, g, (int) x, (int) y, (int) w, (int) h);
1908    }
1909
1910    float getLength(CSS.Attribute JavaDoc key, AttributeSet a) {
1911        return css.getLength(a, key, ss);
1912    }
1913
1914        static boolean isLeftToRight(View v) {
1915            boolean ret = true;
1916            if (isOrientationAware(v)) {
1917                Container container = null;
1918                if (v != null && (container = v.getContainer()) != null) {
1919                    ret = container.getComponentOrientation().isLeftToRight();
1920                }
1921            }
1922            return ret;
1923        }
1924
1925        /*
1926         * only certain tags are concerned about orientation
1927         * <dir>, <menu>, <ul>, <ol>
1928         * for all others we return true. It is implemented this way
1929         * for performance purposes
1930         */

1931        static boolean isOrientationAware(View v) {
1932            boolean ret = false;
1933            AttributeSet attr = null;
1934            Object JavaDoc obj = null;
1935            if (v != null
1936                && (attr = v.getElement().getAttributes()) != null
1937                && (obj = attr.getAttribute(StyleConstants.NameAttribute)) instanceof HTML.Tag JavaDoc
1938                && (obj == HTML.Tag.DIR
1939                    || obj == HTML.Tag.MENU
1940                    || obj == HTML.Tag.UL
1941                    || obj == HTML.Tag.OL)) {
1942                ret = true;
1943            }
1944
1945            return ret;
1946        }
1947
1948        static enum HorizontalMargin { LEFT, RIGHT };
1949
1950        /**
1951         * for <dir>, <menu>, <ul> etc.
1952         * margins are Left-To-Right/Right-To-Left depended.
1953         * see 5088268 for more details
1954         * margin-(left|right)-(ltr|rtl) were introduced to describe it
1955         * if margin-(left|right) is present we are to use it.
1956         *
1957         * @param side The horizontal side to fetch margin for
1958         * This can be HorizontalMargin.LEFT or HorizontalMargin.RIGHT
1959         * @param cssMargin margin from css
1960         * @param a AttributeSet for the View we getting margin for
1961         * @param isLeftToRight
1962         * @return orientation depended margin
1963         */

1964        float getOrientationMargin(HorizontalMargin side, float cssMargin,
1965                                   AttributeSet a, boolean isLeftToRight) {
1966            float margin = cssMargin;
1967            float orientationMargin = cssMargin;
1968            Object JavaDoc cssMarginValue = null;
1969            switch (side) {
1970            case RIGHT:
1971                {
1972                    orientationMargin = (isLeftToRight) ?
1973                        getLength(CSS.Attribute.MARGIN_RIGHT_LTR, a) :
1974                        getLength(CSS.Attribute.MARGIN_RIGHT_RTL, a);
1975                    cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_RIGHT);
1976                }
1977                break;
1978            case LEFT :
1979                {
1980                    orientationMargin = (isLeftToRight) ?
1981                        getLength(CSS.Attribute.MARGIN_LEFT_LTR, a) :
1982                        getLength(CSS.Attribute.MARGIN_LEFT_RTL, a);
1983                    cssMarginValue = a.getAttribute(CSS.Attribute.MARGIN_LEFT);
1984                }
1985                break;
1986            }
1987                
1988            if (cssMarginValue == null
1989                && orientationMargin != Integer.MIN_VALUE) {
1990                margin = orientationMargin;
1991            }
1992            return margin;
1993        }
1994
1995    float topMargin;
1996    float bottomMargin;
1997    float leftMargin;
1998    float rightMargin;
1999    // Bitmask, used to indicate what margins are relative:
2000
// bit 0 for top, 1 for bottom, 2 for left and 3 for right.
2001
short marginFlags;
2002    Border border;
2003    Insets binsets;
2004    CSS JavaDoc css;
2005    StyleSheet JavaDoc ss;
2006    Color bg;
2007    BackgroundImagePainter bgPainter;
2008    }
2009
2010    /**
2011     * Class to carry out some of the duties of CSS list
2012     * formatting. Implementations of this
2013     * class enable views to present the CSS formatting
2014     * while not knowing anything about how the CSS values
2015     * are being cached.
2016     */

2017    public static class ListPainter implements Serializable {
2018
2019    ListPainter(AttributeSet attr, StyleSheet JavaDoc ss) {
2020        this.ss = ss;
2021        /* Get the image to use as a list bullet */
2022        String JavaDoc imgstr = (String JavaDoc)attr.getAttribute(CSS.Attribute.
2023                              LIST_STYLE_IMAGE);
2024        type = null;
2025        if (imgstr != null && !imgstr.equals("none")) {
2026        String JavaDoc tmpstr = null;
2027        try {
2028            StringTokenizer st = new StringTokenizer(imgstr, "()");
2029            if (st.hasMoreTokens())
2030            tmpstr = st.nextToken();
2031            if (st.hasMoreTokens())
2032            tmpstr = st.nextToken();
2033            URL u = new URL(tmpstr);
2034            img = new ImageIcon JavaDoc(u);
2035        } catch (MalformedURLException e) {
2036            if (tmpstr != null && ss != null && ss.getBase() != null) {
2037            try {
2038                URL u = new URL(ss.getBase(), tmpstr);
2039                img = new ImageIcon JavaDoc(u);
2040            } catch (MalformedURLException murle) {
2041                img = null;
2042            }
2043            }
2044            else {
2045            img = null;
2046            }
2047        }
2048        }
2049
2050        /* Get the type of bullet to use in the list */
2051        if (img == null) {
2052        type = (CSS.Value JavaDoc)attr.getAttribute(CSS.Attribute.
2053                            LIST_STYLE_TYPE);
2054        }
2055            start = 1;
2056
2057            paintRect = new Rectangle();
2058    }
2059
2060    /**
2061     * Returns a string that represents the value
2062     * of the HTML.Attribute.TYPE attribute.
2063     * If this attributes is not defined, then
2064     * then the type defaults to "disc" unless
2065     * the tag is on Ordered list. In the case
2066     * of the latter, the default type is "decimal".
2067     */

2068    private CSS.Value JavaDoc getChildType(View childView) {
2069        CSS.Value JavaDoc childtype = (CSS.Value JavaDoc)childView.getAttributes().
2070                                  getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
2071
2072        if (childtype == null) {
2073        if (type == null) {
2074                    // Parent view.
2075
View v = childView.getParent();
2076            HTMLDocument JavaDoc doc = (HTMLDocument JavaDoc)v.getDocument();
2077            if (doc.matchNameAttribute(v.getElement().getAttributes(),
2078                                               HTML.Tag.OL)) {
2079            childtype = CSS.Value.DECIMAL;
2080            } else {
2081            childtype = CSS.Value.DISC;
2082            }
2083        } else {
2084            childtype = type;
2085        }
2086        }
2087        return childtype;
2088    }
2089
2090        /**
2091         * Obtains the starting index from <code>parent</code>.
2092         */

2093        private void getStart(View parent) {
2094            checkedForStart = true;
2095            Element element = parent.getElement();
2096            if (element != null) {
2097                AttributeSet attr = element.getAttributes();
2098                Object JavaDoc startValue;
2099                if (attr != null && attr.isDefined(HTML.Attribute.START) &&
2100                    (startValue = attr.getAttribute
2101                     (HTML.Attribute.START)) != null &&
2102                    (startValue instanceof String JavaDoc)) {
2103
2104                    try {
2105                        start = Integer.parseInt((String JavaDoc)startValue);
2106                    }
2107                    catch (NumberFormatException JavaDoc nfe) {}
2108                }
2109            }
2110        }
2111
2112        /**
2113         * Returns an integer that should be used to render the child at
2114         * <code>childIndex</code> with. The retValue will usually be
2115         * <code>childIndex</code> + 1, unless <code>parentView</code>
2116         * has some Views that do not represent LI's, or one of the views
2117         * has a HTML.Attribute.START specified.
2118         */

2119        private int getRenderIndex(View parentView, int childIndex) {
2120            if (!checkedForStart) {
2121                getStart(parentView);
2122            }
2123            int retIndex = childIndex;
2124            for (int counter = childIndex; counter >= 0; counter--) {
2125                AttributeSet as = parentView.getElement().getElement(counter).
2126                                  getAttributes();
2127                if (as.getAttribute(StyleConstants.NameAttribute) !=
2128                    HTML.Tag.LI) {
2129                    retIndex--;
2130                } else if (as.isDefined(HTML.Attribute.VALUE)) {
2131                    Object JavaDoc value = as.getAttribute(HTML.Attribute.VALUE);
2132                    if (value != null &&
2133                        (value instanceof String JavaDoc)) {
2134                        try {
2135                            int iValue = Integer.parseInt((String JavaDoc)value);
2136                            return retIndex - counter + iValue;
2137                        }
2138                        catch (NumberFormatException JavaDoc nfe) {}
2139                    }
2140                }
2141            }
2142            return retIndex + start;
2143        }
2144
2145    /**
2146     * Paints the CSS list decoration according to the
2147     * attributes given.
2148     *
2149     * @param g the rendering surface.
2150     * @param x the x coordinate of the list item allocation
2151     * @param y the y coordinate of the list item allocation
2152     * @param w the width of the list item allocation
2153     * @param h the height of the list item allocation
2154     * @param v the allocated area to paint into.
2155     * @param item which list item is being painted. This
2156     * is a number greater than or equal to 0.
2157     */

2158        public void paint(Graphics g, float x, float y, float w, float h, View v, int item) {
2159        View cv = v.getView(item);
2160            Object JavaDoc name = cv.getElement().getAttributes().getAttribute
2161                         (StyleConstants.NameAttribute);
2162            // Only draw something if the View is a list item. This won't
2163
// be the case for comments.
2164
if (!(name instanceof HTML.Tag JavaDoc) ||
2165                name != HTML.Tag.LI) {
2166                return;
2167            }
2168        // deside on what side draw bullets, etc.
2169
isLeftToRight =
2170        cv.getContainer().getComponentOrientation().isLeftToRight();
2171
2172            // How the list indicator is aligned is not specified, it is
2173
// left up to the UA. IE and NS differ on this behavior.
2174
// This is closer to NS where we align to the first line of text.
2175
// If the child is not text we draw the indicator at the
2176
// origin (0).
2177
float align = 0;
2178            if (cv.getViewCount() > 0) {
2179                View pView = cv.getView(0);
2180                Object JavaDoc cName = pView.getElement().getAttributes().
2181                               getAttribute(StyleConstants.NameAttribute);
2182                if ((cName == HTML.Tag.P || cName == HTML.Tag.IMPLIED) &&
2183                              pView.getViewCount() > 0) {
2184                    paintRect.setBounds((int)x, (int)y, (int)w, (int)h);
2185                    Shape shape = cv.getChildAllocation(0, paintRect);
2186                    if (shape != null && (shape = pView.getView(0).
2187                                 getChildAllocation(0, shape)) != null) {
2188                        Rectangle rect = (shape instanceof Rectangle) ?
2189                                         (Rectangle)shape : shape.getBounds();
2190
2191                        align = pView.getView(0).getAlignment(View.Y_AXIS);
2192                        y = rect.y;
2193                        h = rect.height;
2194                    }
2195                }
2196            }
2197
2198        // set the color of a decoration
2199
if (ss != null) {
2200        g.setColor(ss.getForeground(cv.getAttributes()));
2201        } else {
2202        g.setColor(Color.black);
2203        }
2204
2205        if (img != null) {
2206            drawIcon(g, (int) x, (int) y, (int) w, (int) h, align,
2207             v.getContainer());
2208        return;
2209        }
2210        CSS.Value JavaDoc childtype = getChildType(cv);
2211        Font font = ((StyledDocument)cv.getDocument()).
2212                                 getFont(cv.getAttributes());
2213        if (font != null) {
2214        g.setFont(font);
2215        }
2216        if (childtype == CSS.Value.SQUARE || childtype == CSS.Value.CIRCLE
2217                || childtype == CSS.Value.DISC) {
2218            drawShape(g, childtype, (int) x, (int) y,
2219              (int) w, (int) h, align);
2220        } else if (childtype == CSS.Value.DECIMAL) {
2221        drawLetter(g, '1', (int) x, (int) y, (int) w, (int) h, align,
2222                           getRenderIndex(v, item));
2223        } else if (childtype == CSS.Value.LOWER_ALPHA) {
2224        drawLetter(g, 'a', (int) x, (int) y, (int) w, (int) h, align,
2225                           getRenderIndex(v, item));
2226        } else if (childtype == CSS.Value.UPPER_ALPHA) {
2227        drawLetter(g, 'A', (int) x, (int) y, (int) w, (int) h, align,
2228                           getRenderIndex(v, item));
2229        } else if (childtype == CSS.Value.LOWER_ROMAN) {
2230        drawLetter(g, 'i', (int) x, (int) y, (int) w, (int) h, align,
2231                           getRenderIndex(v, item));
2232        } else if (childtype == CSS.Value.UPPER_ROMAN) {
2233        drawLetter(g, 'I', (int) x, (int) y, (int) w, (int) h, align,
2234                           getRenderIndex(v, item));
2235        }
2236    }
2237
2238    /**
2239     * Draws the bullet icon specified by the list-style-image argument.
2240     *
2241     * @param g the graphics context
2242     * @param ax x coordinate to place the bullet
2243     * @param ay y coordinate to place the bullet
2244     * @param aw width of the container the bullet is placed in
2245     * @param ah height of the container the bullet is placed in
2246     * @param align preferred alignment factor for the child view
2247     */

2248    void drawIcon(Graphics g, int ax, int ay, int aw, int ah,
2249              float align, Component c) {
2250            // Align to bottom of icon.
2251
int gap = isLeftToRight ? - (img.getIconWidth() + bulletgap) :
2252                                (aw + bulletgap);
2253            int x = ax + gap;
2254            int y = Math.max(ay, ay + (int)(align * ah) -img.getIconHeight());
2255
2256        img.paintIcon(c, g, x, y);
2257    }
2258
2259    /**
2260     * Draws the graphical bullet item specified by the type argument.
2261     *
2262     * @param g the graphics context
2263     * @param type type of bullet to draw (circle, square, disc)
2264     * @param ax x coordinate to place the bullet
2265     * @param ay y coordinate to place the bullet
2266     * @param aw width of the container the bullet is placed in
2267     * @param ah height of the container the bullet is placed in
2268     * @param align preferred alignment factor for the child view
2269     */

2270    void drawShape(Graphics g, CSS.Value JavaDoc type, int ax, int ay, int aw,
2271               int ah, float align) {
2272            // Align to bottom of shape.
2273
int gap = isLeftToRight ? - (bulletgap + 8) : (aw + bulletgap);
2274            int x = ax + gap;
2275            int y = Math.max(ay, ay + (int)(align * ah) - 8);
2276
2277        if (type == CSS.Value.SQUARE) {
2278        g.drawRect(x, y, 8, 8);
2279        } else if (type == CSS.Value.CIRCLE) {
2280        g.drawOval(x, y, 8, 8);
2281        } else {
2282        g.fillOval(x, y, 8, 8);
2283        }
2284    }
2285
2286    /**
2287     * Draws the letter or number for an ordered list.
2288     *
2289     * @param g the graphics context
2290     * @param letter type of ordered list to draw
2291     * @param ax x coordinate to place the bullet
2292     * @param ay y coordinate to place the bullet
2293     * @param aw width of the container the bullet is placed in
2294     * @param ah height of the container the bullet is placed in
2295     * @param index position of the list item in the list
2296     */

2297    void drawLetter(Graphics g, char letter, int ax, int ay, int aw,
2298            int ah, float align, int index) {
2299        String JavaDoc str = formatItemNum(index, letter);
2300            str = isLeftToRight ? str + "." : "." + str;
2301        FontMetrics fm = SwingUtilities2.getFontMetrics(null, g);
2302        int stringwidth = SwingUtilities2.stringWidth(null, fm, str);
2303            int gap = isLeftToRight ? - (stringwidth + bulletgap) :
2304                                (aw + bulletgap);
2305            int x = ax + gap;
2306        int y = Math.max(ay + fm.getAscent(), ay + (int)(ah * align));
2307        SwingUtilities2.drawString(null, g, str, x, y);
2308    }
2309
2310    /**
2311     * Converts the item number into the ordered list number
2312     * (i.e. 1 2 3, i ii iii, a b c, etc.
2313     *
2314     * @param itemNum number to format
2315     * @param type type of ordered list
2316     */

2317    String JavaDoc formatItemNum(int itemNum, char type) {
2318        String JavaDoc numStyle = "1";
2319
2320        boolean uppercase = false;
2321
2322        String JavaDoc formattedNum;
2323
2324        switch (type) {
2325        case '1':
2326        default:
2327        formattedNum = String.valueOf(itemNum);
2328        break;
2329
2330        case 'A':
2331        uppercase = true;
2332        // fall through
2333
case 'a':
2334        formattedNum = formatAlphaNumerals(itemNum);
2335        break;
2336
2337        case 'I':
2338        uppercase = true;
2339        // fall through
2340
case 'i':
2341        formattedNum = formatRomanNumerals(itemNum);
2342        }
2343
2344        if (uppercase) {
2345        formattedNum = formattedNum.toUpperCase();
2346        }
2347
2348        return formattedNum;
2349    }
2350
2351    /**
2352     * Converts the item number into an alphabetic character
2353     *
2354     * @param itemNum number to format
2355     */

2356    String JavaDoc formatAlphaNumerals(int itemNum) {
2357        String JavaDoc result = "";
2358
2359        if (itemNum > 26) {
2360        result = formatAlphaNumerals(itemNum / 26) +
2361            formatAlphaNumerals(itemNum % 26);
2362        } else {
2363        // -1 because item is 1 based.
2364
result = String.valueOf((char)('a' + itemNum - 1));
2365        }
2366
2367        return result;
2368    }
2369
2370    /* list of roman numerals */
2371    static final char romanChars[][] = {
2372        {'i', 'v'},
2373        {'x', 'l' },
2374        {'c', 'd' },
2375        {'m', '?' },
2376        };
2377
2378    /**
2379     * Converts the item number into a roman numeral
2380     *
2381     * @param num number to format
2382     */

2383    String JavaDoc formatRomanNumerals(int num) {
2384        return formatRomanNumerals(0, num);
2385    }
2386
2387    /**
2388     * Converts the item number into a roman numeral
2389     *
2390     * @param num number to format
2391     */

2392    String JavaDoc formatRomanNumerals(int level, int num) {
2393        if (num < 10) {
2394        return formatRomanDigit(level, num);
2395        } else {
2396        return formatRomanNumerals(level + 1, num / 10) +
2397            formatRomanDigit(level, num % 10);
2398        }
2399    }
2400
2401
2402    /**
2403     * Converts the item number into a roman numeral
2404     *
2405     * @param level position
2406     * @param num digit to format
2407     */

2408    String JavaDoc formatRomanDigit(int level, int digit) {
2409        String JavaDoc result = "";
2410        if (digit == 9) {
2411        result = result + romanChars[level][0];
2412        result = result + romanChars[level + 1][0];
2413        return result;
2414        } else if (digit == 4) {
2415        result = result + romanChars[level][0];
2416        result = result + romanChars[level][1];
2417        return result;
2418        } else if (digit >= 5) {
2419        result = result + romanChars[level][1];
2420        digit -= 5;
2421        }
2422
2423        for (int i = 0; i < digit; i++) {
2424        result = result + romanChars[level][0];
2425        }
2426
2427        return result;
2428    }
2429
2430        private Rectangle paintRect;
2431        private boolean checkedForStart;
2432        private int start;
2433        private CSS.Value JavaDoc type;
2434        URL imageurl;
2435        private StyleSheet JavaDoc ss = null;
2436        Icon JavaDoc img = null;
2437        private int bulletgap = 5;
2438    private boolean isLeftToRight;
2439    }
2440
2441
2442    /**
2443     * Paints the background image.
2444     */

2445    static class BackgroundImagePainter implements Serializable {
2446    ImageIcon JavaDoc backgroundImage;
2447    float hPosition;
2448    float vPosition;
2449    // bit mask: 0 for repeat x, 1 for repeat y, 2 for horiz relative,
2450
// 3 for vert relative
2451
short flags;
2452    // These are used when painting, updatePaintCoordinates updates them.
2453
private int paintX;
2454    private int paintY;
2455    private int paintMaxX;
2456    private int paintMaxY;
2457
2458    BackgroundImagePainter(AttributeSet a, CSS JavaDoc css, StyleSheet JavaDoc ss) {
2459        backgroundImage = ss.getBackgroundImage(a);
2460        // Determine the position.
2461
CSS.BackgroundPosition JavaDoc pos = (CSS.BackgroundPosition JavaDoc)a.getAttribute
2462                                   (CSS.Attribute.BACKGROUND_POSITION);
2463        if (pos != null) {
2464        hPosition = pos.getHorizontalPosition();
2465        vPosition = pos.getVerticalPosition();
2466        if (pos.isHorizontalPositionRelativeToSize()) {
2467            flags |= 4;
2468        }
2469        else if (pos.isHorizontalPositionRelativeToSize()) {
2470            hPosition *= css.getFontSize(a, 12, ss);
2471        }
2472        if (pos.isVerticalPositionRelativeToSize()) {
2473            flags |= 8;
2474        }
2475        else if (pos.isVerticalPositionRelativeToFontSize()) {
2476            vPosition *= css.getFontSize(a, 12, ss);
2477        }
2478        }
2479        // Determine any repeating values.
2480
CSS.Value JavaDoc repeats = (CSS.Value JavaDoc)a.getAttribute(CSS.Attribute.
2481                              BACKGROUND_REPEAT);
2482        if (repeats == null || repeats == CSS.Value.BACKGROUND_REPEAT) {
2483        flags |= 3;
2484        }
2485        else if (repeats == CSS.Value.BACKGROUND_REPEAT_X) {
2486        flags |= 1;
2487        }
2488        else if (repeats == CSS.Value.BACKGROUND_REPEAT_Y) {
2489        flags |= 2;
2490        }
2491    }
2492
2493        void paint(Graphics g, float x, float y, float w, float h, View v) {
2494        Rectangle clip = g.getClipRect();
2495        if (clip != null) {
2496        // Constrain the clip so that images don't draw outside the
2497
// legal bounds.
2498
g.clipRect((int)x, (int)y, (int)w, (int)h);
2499        }
2500        if ((flags & 3) == 0) {
2501        // no repeating
2502
int width = backgroundImage.getIconWidth();
2503        int height = backgroundImage.getIconWidth();
2504        if ((flags & 4) == 4) {
2505            paintX = (int)(x + w * hPosition -
2506                  (float)width * hPosition);
2507        }
2508        else {
2509            paintX = (int)x + (int)hPosition;
2510        }
2511        if ((flags & 8) == 8) {
2512            paintY = (int)(y + h * vPosition -
2513                  (float)height * vPosition);
2514        }
2515        else {
2516            paintY = (int)y + (int)vPosition;
2517        }
2518        if (clip == null ||
2519            !((paintX + width <= clip.x) ||
2520              (paintY + height <= clip.y) ||
2521              (paintX >= clip.x + clip.width) ||
2522              (paintY >= clip.y + clip.height))) {
2523            backgroundImage.paintIcon(null, g, paintX, paintY);
2524        }
2525        }
2526        else {
2527        int width = backgroundImage.getIconWidth();
2528        int height = backgroundImage.getIconHeight();
2529        if (width > 0 && height > 0) {
2530            paintX = (int)x;
2531            paintY = (int)y;
2532            paintMaxX = (int)(x + w);
2533            paintMaxY = (int)(y + h);
2534            if (updatePaintCoordinates(clip, width, height)) {
2535            while (paintX < paintMaxX) {
2536                int ySpot = paintY;
2537                while (ySpot < paintMaxY) {
2538                backgroundImage.paintIcon(null, g, paintX,
2539                              ySpot);
2540                ySpot += height;
2541                }
2542                paintX += width;
2543            }
2544            }
2545        }
2546        }
2547        if (clip != null) {
2548        // Reset clip.
2549
g.setClip(clip.x, clip.y, clip.width, clip.height);
2550        }
2551    }
2552
2553    private boolean updatePaintCoordinates
2554             (Rectangle clip, int width, int height){
2555        if ((flags & 3) == 1) {
2556        paintMaxY = paintY + 1;
2557        }
2558        else if ((flags & 3) == 2) {
2559        paintMaxX = paintX + 1;
2560        }
2561        if (clip != null) {
2562        if ((flags & 3) == 1 && ((paintY + height <= clip.y) ||
2563                     (paintY > clip.y + clip.height))) {
2564            // not visible.
2565
return false;
2566        }
2567        if ((flags & 3) == 2 && ((paintX + width <= clip.x) ||
2568                     (paintX > clip.x + clip.width))) {
2569            // not visible.
2570
return false;
2571        }
2572        if ((flags & 1) == 1) {
2573            if ((clip.x + clip.width) < paintMaxX) {
2574            if ((clip.x + clip.width - paintX) % width == 0) {
2575                paintMaxX = clip.x + clip.width;
2576            }
2577            else {
2578                paintMaxX = ((clip.x + clip.width - paintX) /
2579                     width + 1) * width + paintX;
2580            }
2581            }
2582            if (clip.x > paintX) {
2583            paintX = (clip.x - paintX) / width * width + paintX;
2584            }
2585        }
2586        if ((flags & 2) == 2) {
2587            if ((clip.y + clip.height) < paintMaxY) {
2588            if ((clip.y + clip.height - paintY) % height == 0) {
2589                paintMaxY = clip.y + clip.height;
2590            }
2591            else {
2592                paintMaxY = ((clip.y + clip.height - paintY) /
2593                     height + 1) * height + paintY;
2594            }
2595            }
2596            if (clip.y > paintY) {
2597            paintY = (clip.y - paintY) / height * height + paintY;
2598            }
2599        }
2600        }
2601        // Valid
2602
return true;
2603    }
2604    }
2605
2606
2607    /**
2608     * A subclass of MuxingAttributeSet that translates between
2609     * CSS and HTML and StyleConstants. The AttributeSets used are
2610     * the CSS rules that match the Views Elements.
2611     */

2612    class ViewAttributeSet extends MuxingAttributeSet JavaDoc {
2613    ViewAttributeSet(View v) {
2614        host = v;
2615
2616        // PENDING(prinz) fix this up to be a more realistic
2617
// implementation.
2618
Document doc = v.getDocument();
2619        SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
2620        Vector muxList = sb.getVector();
2621        try {
2622        if (doc instanceof HTMLDocument JavaDoc) {
2623            StyleSheet JavaDoc styles = StyleSheet.this;
2624            Element elem = v.getElement();
2625            AttributeSet a = elem.getAttributes();
2626            AttributeSet htmlAttr = styles.translateHTMLToCSS(a);
2627
2628            if (htmlAttr.getAttributeCount() != 0) {
2629            muxList.addElement(htmlAttr);
2630            }
2631            if (elem.isLeaf()) {
2632            Enumeration keys = a.getAttributeNames();
2633            while (keys.hasMoreElements()) {
2634                Object JavaDoc key = keys.nextElement();
2635                if (key instanceof HTML.Tag JavaDoc) {
2636                if ((HTML.Tag JavaDoc)key == HTML.Tag.A) {
2637                    Object JavaDoc o = a.getAttribute((HTML.Tag JavaDoc)key);
2638                /**
2639                   In the case of an A tag, the css rules
2640                   apply only for tags that have their
2641                   href attribute defined and not for
2642                   anchors that only have their name attributes
2643                   defined, i.e anchors that function as
2644                   destinations. Hence we do not add the
2645                   attributes for that latter kind of
2646                   anchors. When CSS2 support is added,
2647                   it will be possible to specificity this
2648                   kind of conditional behaviour in the
2649                   stylesheet.
2650                 **/

2651                    if (o != null && o instanceof AttributeSet) {
2652                    AttributeSet attr = (AttributeSet)o;
2653                    if (attr.getAttribute(HTML.Attribute.HREF) == null) {
2654                        continue;
2655                    }
2656                    }
2657                }
2658                AttributeSet cssRule = styles.getRule((HTML.Tag JavaDoc) key, elem);
2659                if (cssRule != null) {
2660                    muxList.addElement(cssRule);
2661                }
2662                }
2663            }
2664            } else {
2665            HTML.Tag JavaDoc t = (HTML.Tag JavaDoc) a.getAttribute
2666                         (StyleConstants.NameAttribute);
2667            AttributeSet cssRule = styles.getRule(t, elem);
2668            if (cssRule != null) {
2669                muxList.addElement(cssRule);
2670            }
2671            }
2672        }
2673        AttributeSet[] attrs = new AttributeSet[muxList.size()];
2674        muxList.copyInto(attrs);
2675                setAttributes(attrs);
2676        }
2677        finally {
2678        SearchBuffer.releaseSearchBuffer(sb);
2679        }
2680    }
2681
2682    // --- AttributeSet methods ----------------------------
2683

2684    /**
2685     * Checks whether a given attribute is defined.
2686     * This will convert the key over to CSS if the
2687     * key is a StyleConstants key that has a CSS
2688     * mapping.
2689     *
2690     * @param key the attribute key
2691     * @return true if the attribute is defined
2692     * @see AttributeSet#isDefined
2693     */

2694        public boolean isDefined(Object JavaDoc key) {
2695        if (key instanceof StyleConstants) {
2696        Object JavaDoc cssKey = css.styleConstantsKeyToCSSKey
2697                            ((StyleConstants)key);
2698        if (cssKey != null) {
2699            key = cssKey;
2700        }
2701        }
2702        return super.isDefined(key);
2703    }
2704
2705    /**
2706     * Gets the value of an attribute. If the requested
2707     * attribute is a StyleConstants attribute that has
2708     * a CSS mapping, the request will be converted.
2709     *
2710     * @param key the attribute name
2711     * @return the attribute value
2712     * @see AttributeSet#getAttribute
2713     */

2714        public Object JavaDoc getAttribute(Object JavaDoc key) {
2715        if (key instanceof StyleConstants) {
2716        Object JavaDoc cssKey = css.styleConstantsKeyToCSSKey
2717                       ((StyleConstants)key);
2718        if (cssKey != null) {
2719            Object JavaDoc value = doGetAttribute(cssKey);
2720            if (value instanceof CSS.CssValue JavaDoc) {
2721            return ((CSS.CssValue JavaDoc)value).toStyleConstants
2722                         ((StyleConstants)key, host);
2723            }
2724        }
2725        }
2726        return doGetAttribute(key);
2727    }
2728
2729        Object JavaDoc doGetAttribute(Object JavaDoc key) {
2730        Object JavaDoc retValue = super.getAttribute(key);
2731        if (retValue != null) {
2732        return retValue;
2733        }
2734        // didn't find it... try parent if it's a css attribute
2735
// that is inherited.
2736
if (key instanceof CSS.Attribute JavaDoc) {
2737        CSS.Attribute JavaDoc css = (CSS.Attribute JavaDoc) key;
2738        if (css.isInherited()) {
2739            AttributeSet parent = getResolveParent();
2740            if (parent != null)
2741            return parent.getAttribute(key);
2742        }
2743        }
2744        return null;
2745    }
2746
2747    /**
2748     * If not overriden, the resolving parent defaults to
2749     * the parent element.
2750     *
2751     * @return the attributes from the parent
2752     * @see AttributeSet#getResolveParent
2753     */

2754        public AttributeSet getResolveParent() {
2755        if (host == null) {
2756        return null;
2757        }
2758        View parent = host.getParent();
2759        return (parent != null) ? parent.getAttributes() : null;
2760    }
2761
2762    /** View created for. */
2763    View host;
2764    }
2765
2766
2767    /**
2768     * A subclass of MuxingAttributeSet that implements Style. Currently
2769     * the MutableAttributeSet methods are unimplemented, that is they
2770     * do nothing.
2771     */

2772    // PENDING(sky): Decide what to do with this. Either make it
2773
// contain a SimpleAttributeSet that modify methods are delegated to,
2774
// or change getRule to return an AttributeSet and then don't make this
2775
// implement Style.
2776
static class ResolvedStyle extends MuxingAttributeSet JavaDoc implements
2777                  Serializable, Style {
2778    ResolvedStyle(String JavaDoc name, AttributeSet[] attrs, int extendedIndex) {
2779        super(attrs);
2780        this.name = name;
2781        this.extendedIndex = extendedIndex;
2782    }
2783
2784        /**
2785         * Inserts a Style into the receiver so that the styles the
2786         * receiver represents are still ordered by specificity.
2787     * <code>style</code> will be added before any extended styles, that
2788     * is before extendedIndex.
2789         */

2790        synchronized void insertStyle(Style style, int specificity) {
2791            AttributeSet[] attrs = getAttributes();
2792            int maxCounter = attrs.length;
2793        int counter = 0;
2794            for (;counter < extendedIndex; counter++) {
2795        if (specificity > getSpecificity(((Style)attrs[counter]).
2796                         getName())) {
2797            break;
2798                }
2799            }
2800        insertAttributeSetAt(style, counter);
2801        extendedIndex++;
2802    }
2803
2804    /**
2805     * Removes a previously added style. This will do nothing if
2806     * <code>style</code> is not referenced by the receiver.
2807     */

2808    synchronized void removeStyle(Style style) {
2809            AttributeSet[] attrs = getAttributes();
2810
2811        for (int counter = attrs.length - 1; counter >= 0; counter--) {
2812        if (attrs[counter] == style) {
2813            removeAttributeSetAt(counter);
2814            if (counter < extendedIndex) {
2815            extendedIndex--;
2816            }
2817            break;
2818        }
2819        }
2820    }
2821
2822    /**
2823     * Adds <code>s</code> as one of the Attributesets to look up
2824     * attributes in.
2825     */

2826    synchronized void insertExtendedStyleAt(Style attr, int index) {
2827        insertAttributeSetAt(attr, extendedIndex + index);
2828    }
2829
2830    /**
2831     * Adds <code>s</code> as one of the AttributeSets to look up
2832     * attributes in. It will be the AttributeSet last checked.
2833     */

2834    synchronized void addExtendedStyle(Style attr) {
2835        insertAttributeSetAt(attr, getAttributes().length);
2836    }
2837
2838    /**
2839     * Removes the style at <code>index</code> +
2840     * <code>extendedIndex</code>.
2841     */

2842    synchronized void removeExtendedStyleAt(int index) {
2843        removeAttributeSetAt(extendedIndex + index);
2844    }
2845
2846        /**
2847         * Returns true if the receiver matches <code>selector</code>, where
2848         * a match is defined by the CSS rule matching.
2849     * Each simple selector must be separated by a single space.
2850         */

2851        protected boolean matches(String JavaDoc selector) {
2852            int sLast = selector.length();
2853
2854            if (sLast == 0) {
2855                return false;
2856            }
2857            int thisLast = name.length();
2858            int sCurrent = selector.lastIndexOf(' ');
2859            int thisCurrent = name.lastIndexOf(' ');
2860        if (sCurrent >= 0) {
2861        sCurrent++;
2862        }
2863        if (thisCurrent >= 0) {
2864        thisCurrent++;
2865        }
2866            if (!matches(selector, sCurrent, sLast, thisCurrent, thisLast)) {
2867                return false;
2868            }
2869            while (sCurrent != -1) {
2870                sLast = sCurrent - 1;
2871                sCurrent = selector.lastIndexOf(' ', sLast - 1);
2872        if (sCurrent >= 0) {
2873            sCurrent++;
2874        }
2875                boolean match = false;
2876                while (!match && thisCurrent != -1) {
2877                    thisLast = thisCurrent - 1;
2878                    thisCurrent = name.lastIndexOf(' ', thisLast - 1);
2879            if (thisCurrent >= 0) {
2880            thisCurrent++;
2881            }
2882                    match = matches(selector, sCurrent, sLast, thisCurrent,
2883                    thisLast);
2884                }
2885                if (!match) {
2886                    return false;
2887                }
2888            }
2889            return true;
2890        }
2891
2892        /**
2893         * Returns true if the substring of the receiver, in the range
2894         * thisCurrent, thisLast matches the substring of selector in
2895         * the ranme sCurrent to sLast based on CSS selector matching.
2896         */

2897        boolean matches(String JavaDoc selector, int sCurrent, int sLast,
2898                       int thisCurrent, int thisLast) {
2899            sCurrent = Math.max(sCurrent, 0);
2900            thisCurrent = Math.max(thisCurrent, 0);
2901            int thisDotIndex = boundedIndexOf(name, '.', thisCurrent,
2902                          thisLast);
2903            int thisPoundIndex = boundedIndexOf(name, '#', thisCurrent,
2904                        thisLast);
2905            int sDotIndex = boundedIndexOf(selector, '.', sCurrent, sLast);
2906            int sPoundIndex = boundedIndexOf(selector, '#', sCurrent, sLast);
2907            if (sDotIndex != -1) {
2908                // Selector has a '.', which indicates name must match it,
2909
// or if the '.' starts the selector than name must have
2910
// the same class (doesn't matter what element name).
2911
if (thisDotIndex == -1) {
2912                    return false;
2913                }
2914                if (sCurrent == sDotIndex) {
2915                    if ((thisLast - thisDotIndex) != (sLast - sDotIndex) ||
2916                        !selector.regionMatches(sCurrent, name, thisDotIndex,
2917                                                (thisLast - thisDotIndex))) {
2918                        return false;
2919                    }
2920                }
2921                else {
2922                    // Has to fully match.
2923
if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
2924                        !selector.regionMatches(sCurrent, name, thisCurrent,
2925                                                (thisLast - thisCurrent))) {
2926                        return false;
2927                    }
2928                }
2929                return true;
2930            }
2931            if (sPoundIndex != -1) {
2932                // Selector has a '#', which indicates name must match it,
2933
// or if the '#' starts the selector than name must have
2934
// the same id (doesn't matter what element name).
2935
if (thisPoundIndex == -1) {
2936                    return false;
2937                }
2938                if (sCurrent == sPoundIndex) {
2939                    if ((thisLast - thisPoundIndex) !=(sLast - sPoundIndex) ||
2940                        !selector.regionMatches(sCurrent, name, thisPoundIndex,
2941                                                (thisLast - thisPoundIndex))) {
2942                        return false;
2943                    }
2944                }
2945                else {
2946                    // Has to fully match.
2947
if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
2948                        !selector.regionMatches(sCurrent, name, thisCurrent,
2949                                               (thisLast - thisCurrent))) {
2950                        return false;
2951                    }
2952                }
2953                return true;
2954            }
2955            if (thisDotIndex != -1) {
2956                // Reciever references a class, just check element name.
2957
return (((thisDotIndex - thisCurrent) == (sLast - sCurrent)) &&
2958                        selector.regionMatches(sCurrent, name, thisCurrent,
2959                                               thisDotIndex - thisCurrent));
2960            }
2961            if (thisPoundIndex != -1) {
2962                // Reciever references an id, just check element name.
2963
return (((thisPoundIndex - thisCurrent) ==(sLast - sCurrent))&&
2964                        selector.regionMatches(sCurrent, name, thisCurrent,
2965                                               thisPoundIndex - thisCurrent));
2966            }
2967            // Fail through, no classes or ides, just check string.
2968
return (((thisLast - thisCurrent) == (sLast - sCurrent)) &&
2969                    selector.regionMatches(sCurrent, name, thisCurrent,
2970                                           thisLast - thisCurrent));
2971        }
2972
2973        /**
2974         * Similiar to String.indexOf, but allows an upper bound
2975         * (this is slower in that it will still check string starting at
2976         * start.
2977         */

2978        int boundedIndexOf(String JavaDoc string, char search, int start,
2979                           int end) {
2980            int retValue = string.indexOf(search, start);
2981            if (retValue >= end) {
2982                return -1;
2983            }
2984            return retValue;
2985        }
2986
2987    public void addAttribute(Object JavaDoc name, Object JavaDoc value) {}
2988    public void addAttributes(AttributeSet attributes) {}
2989    public void removeAttribute(Object JavaDoc name) {}
2990    public void removeAttributes(Enumeration<?> names) {}
2991    public void removeAttributes(AttributeSet attributes) {}
2992    public void setResolveParent(AttributeSet parent) {}
2993    public String JavaDoc getName() {return name;}
2994    public void addChangeListener(ChangeListener JavaDoc l) {}
2995    public void removeChangeListener(ChangeListener JavaDoc l) {}
2996        public ChangeListener JavaDoc[] getChangeListeners() {
2997            return new ChangeListener JavaDoc[0];
2998        }
2999
3000    /** The name of the Style, which is the selector.
3001     * This will NEVER change!
3002     */

3003    String JavaDoc name;
3004    /** Start index of styles coming from other StyleSheets. */
3005    private int extendedIndex;
3006    }
3007
3008
3009    /**
3010     * SelectorMapping contains a specifitiy, as an integer, and an associated
3011     * Style. It can also reference children <code>SelectorMapping</code>s,
3012     * so that it behaves like a tree.
3013     * <p>
3014     * This is not thread safe, it is assumed the caller will take the
3015     * necessary precations if this is to be used in a threaded environment.
3016     */

3017    static class SelectorMapping implements Serializable {
3018        public SelectorMapping(int specificity) {
3019            this.specificity = specificity;
3020        }
3021
3022        /**
3023         * Returns the specificity this mapping represents.
3024         */

3025        public int getSpecificity() {
3026            return specificity;
3027        }
3028
3029        /**
3030         * Sets the Style associated with this mapping.
3031         */

3032        public void setStyle(Style style) {
3033            this.style = style;
3034        }
3035
3036        /**
3037         * Returns the Style associated with this mapping.
3038         */

3039        public Style getStyle() {
3040            return style;
3041        }
3042
3043        /**
3044         * Returns the child mapping identified by the simple selector
3045         * <code>selector</code>. If a child mapping does not exist for
3046         *<code>selector</code>, and <code>create</code> is true, a new
3047         * one will be created.
3048         */

3049        public SelectorMapping getChildSelectorMapping(String JavaDoc selector,
3050                                                       boolean create) {
3051            SelectorMapping retValue = null;
3052
3053            if (children != null) {
3054                retValue = (SelectorMapping)children.get(selector);
3055            }
3056            else if (create) {
3057                children = new HashMap(7);
3058            }
3059            if (retValue == null && create) {
3060                int specificity = getChildSpecificity(selector);
3061
3062                retValue = createChildSelectorMapping(specificity);
3063                children.put(selector, retValue);
3064            }
3065            return retValue;
3066        }
3067
3068        /**
3069         * Creates a child <code>SelectorMapping</code> with the specified
3070         * <code>specificity</code>.
3071         */

3072        protected SelectorMapping createChildSelectorMapping(int specificity) {
3073            return new SelectorMapping(specificity);
3074        }
3075
3076        /**
3077         * Returns the specificity for the child selector
3078         * <code>selector</code>.
3079         */

3080        protected int getChildSpecificity(String JavaDoc selector) {
3081        // class (.) 100
3082
// id (#) 10000
3083
char firstChar = selector.charAt(0);
3084            int specificity = getSpecificity();
3085
3086        if (firstChar == '.') {
3087        specificity += 100;
3088        }
3089        else if (firstChar == '#') {
3090        specificity += 10000;
3091        }
3092        else {
3093        specificity += 1;
3094        if (selector.indexOf('.') != -1) {
3095            specificity += 100;
3096        }
3097        if (selector.indexOf('#') != -1) {
3098            specificity += 10000;
3099        }
3100        }
3101            return specificity;
3102        }
3103
3104        /**
3105         * The specificity for this selector.
3106         */

3107        private int specificity;
3108        /**
3109         * Style for this selector.
3110         */

3111        private Style style;
3112        /**
3113         * Any sub selectors. Key will be String, and value will be
3114         * another SelectorMapping.
3115         */

3116        private HashMap children;
3117    }
3118
3119
3120    // ---- Variables ---------------------------------------------
3121

3122    final static int DEFAULT_FONT_SIZE = 3;
3123
3124    private CSS JavaDoc css;
3125
3126    /**
3127     * An inverted graph of the selectors.
3128     */

3129    private SelectorMapping selectorMapping;
3130
3131    /** Maps from selector (as a string) to Style that includes all
3132     * relevant styles. */

3133    private Hashtable resolvedStyles;
3134
3135    /** Vector of StyleSheets that the rules are to reference.
3136     */

3137    private Vector linkedStyleSheets;
3138
3139    /** Where the style sheet was found. Used for relative imports. */
3140    private URL base;
3141
3142
3143    /**
3144     * Default parser for CSS specifications that get loaded into
3145     * the StyleSheet.<p>
3146     * This class is NOT thread safe, do not ask it to parse while it is
3147     * in the middle of parsing.
3148     */

3149    class CssParser implements CSSParser.CSSParserCallback JavaDoc {
3150
3151    /**
3152     * Parses the passed in CSS declaration into an AttributeSet.
3153     */

3154    public AttributeSet parseDeclaration(String JavaDoc string) {
3155        try {
3156        return parseDeclaration(new StringReader(string));
3157        } catch (IOException ioe) {}
3158        return null;
3159    }
3160
3161    /**
3162     * Parses the passed in CSS declaration into an AttributeSet.
3163     */

3164    public AttributeSet parseDeclaration(Reader r) throws IOException {
3165        parse(base, r, true, false);
3166        return declaration.copyAttributes();
3167    }
3168
3169    /**
3170     * Parse the given CSS stream
3171     */

3172    public void parse(URL base, Reader r, boolean parseDeclaration,
3173              boolean isLink) throws IOException {
3174        this.base = base;
3175        this.isLink = isLink;
3176        this.parsingDeclaration = parseDeclaration;
3177        declaration.removeAttributes(declaration);
3178        selectorTokens.removeAllElements();
3179        selectors.removeAllElements();
3180        propertyName = null;
3181        parser.parse(r, this, parseDeclaration);
3182    }
3183
3184    //
3185
// CSSParserCallback methods, public to implement the interface.
3186
//
3187

3188    /**
3189     * Invoked when a valid @import is encountered, will call
3190     * <code>importStyleSheet</code> if a
3191     * <code>MalformedURLException</code> is not thrown in creating
3192     * the URL.
3193     */

3194    public void handleImport(String JavaDoc importString) {
3195        URL url = CSS.getURL(base, importString);
3196        if (url != null) {
3197        importStyleSheet(url);
3198        }
3199    }
3200
3201    /**
3202     * A selector has been encountered.
3203     */

3204    public void handleSelector(String JavaDoc selector) {
3205            selector = selector.toLowerCase();
3206
3207        int length = selector.length();
3208
3209        if (selector.endsWith(",")) {
3210        if (length > 1) {
3211            selector = selector.substring(0, length - 1);
3212            selectorTokens.addElement(selector);
3213        }
3214        addSelector();
3215        }
3216        else if (length > 0) {
3217        selectorTokens.addElement(selector);
3218        }
3219    }
3220
3221    /**
3222     * Invoked when the start of a rule is encountered.
3223     */

3224    public void startRule() {
3225        if (selectorTokens.size() > 0) {
3226        addSelector();
3227        }
3228        propertyName = null;
3229    }
3230
3231    /**
3232     * Invoked when a property name is encountered.
3233     */

3234    public void handleProperty(String JavaDoc property) {
3235        propertyName = property;
3236    }
3237
3238    /**
3239     * Invoked when a property value is encountered.
3240     */

3241    public void handleValue(String JavaDoc value) {
3242        if (propertyName != null && value != null && value.length() > 0) {
3243        CSS.Attribute JavaDoc cssKey = CSS.getAttribute(propertyName);
3244        if (cssKey != null) {
3245                    // There is currently no mechanism to determine real
3246
// base that style sheet was loaded from. For the time
3247
// being, this maps for LIST_STYLE_IMAGE, which appear
3248
// to be the only one that currently matters. A more
3249
// general mechanism is definately needed.
3250
if (cssKey == CSS.Attribute.LIST_STYLE_IMAGE) {
3251                        if (value != null && !value.equals("none")) {
3252                            URL url = CSS.getURL(base, value);
3253
3254                            if (url != null) {
3255                                value = url.toString();
3256                            }
3257                        }
3258                    }
3259            addCSSAttribute(declaration, cssKey, value);
3260        }
3261        propertyName = null;
3262        }
3263    }
3264
3265    /**
3266     * Invoked when the end of a rule is encountered.
3267     */

3268    public void endRule() {
3269        int n = selectors.size();
3270        for (int i = 0; i < n; i++) {
3271        String JavaDoc[] selector = (String JavaDoc[]) selectors.elementAt(i);
3272        if (selector.length > 0) {
3273            StyleSheet.this.addRule(selector, declaration, isLink);
3274        }
3275        }
3276        declaration.removeAttributes(declaration);
3277        selectors.removeAllElements();
3278    }
3279
3280    private void addSelector() {
3281        String JavaDoc[] selector = new String JavaDoc[selectorTokens.size()];
3282        selectorTokens.copyInto(selector);
3283        selectors.addElement(selector);
3284        selectorTokens.removeAllElements();
3285    }
3286
3287
3288    Vector selectors = new Vector();
3289    Vector selectorTokens = new Vector();
3290    /** Name of the current property. */
3291    String JavaDoc propertyName;
3292    MutableAttributeSet declaration = new SimpleAttributeSet();
3293    /** True if parsing a declaration, that is the Reader will not
3294     * contain a selector. */

3295    boolean parsingDeclaration;
3296    /** True if the attributes are coming from a linked/imported style. */
3297    boolean isLink;
3298    /** Where the CSS stylesheet lives. */
3299    URL base;
3300    CSSParser JavaDoc parser = new CSSParser JavaDoc();
3301    }
3302
3303    void rebaseSizeMap(int base) {
3304        final int minimalFontSize = 4;
3305        sizeMap = new int[sizeMapDefault.length];
3306        for (int i = 0; i < sizeMapDefault.length; i++) {
3307            sizeMap[i] = Math.max(base * sizeMapDefault[i] /
3308                                  sizeMapDefault[CSS.baseFontSizeIndex],
3309                                  minimalFontSize);
3310        }
3311
3312    }
3313
3314    int[] getSizeMap() {
3315        return sizeMap;
3316    }
3317    boolean isW3CLengthUnits() {
3318        return w3cLengthUnits;
3319    }
3320
3321    /**
3322     * The HTML/CSS size model has seven slots
3323     * that one can assign sizes to.
3324     */

3325    static int sizeMapDefault[] = { 8, 10, 12, 14, 18, 24, 36 };
3326
3327    private int sizeMap[] = sizeMapDefault;
3328    private boolean w3cLengthUnits = false;
3329}
3330
Popular Tags