1. /*
  2. * @(#)StyleSheet.java 1.63 00/02/02
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.text.html;
  11. import java.util.*;
  12. import java.awt.*;
  13. import java.io.*;
  14. import java.net.*;
  15. import javax.swing.Icon;
  16. import javax.swing.ImageIcon;
  17. import javax.swing.border.*;
  18. import javax.swing.event.ChangeListener;
  19. import javax.swing.text.*;
  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. *  
  60. *   import java.util.*;
  61. *   import javax.swing.text.*;
  62. *   import javax.swing.text.html.*;
  63. *  
  64. *   public class ShowStyles {
  65. *  
  66. *   public static void main(String[] args) {
  67. *   HTMLEditorKit kit = new HTMLEditorKit();
  68. *   HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
  69. *   StyleSheet styles = doc.getStyleSheet();
  70. *  
  71. *   Enumeration rules = styles.getStyleNames();
  72. *   while (rules.hasMoreElements()) {
  73. *   String name = (String) rules.nextElement();
  74. *   Style rule = styles.getStyle(name);
  75. *   System.out.println(rule.toString());
  76. *   }
  77. *   System.exit(0);
  78. *   }
  79. *   }
  80. *  
  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><body bgcolor=red></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.63 02/02/00
  111. */
  112. public class StyleSheet extends StyleContext {
  113. /**
  114. * Construct a StyleSheet
  115. */
  116. public StyleSheet() {
  117. super();
  118. selectorMapping = new Hashtable();
  119. resolvedStyles = new Hashtable();
  120. if (css == null) {
  121. css = new CSS();
  122. }
  123. }
  124. /**
  125. * Fetches the style to use to render the given type
  126. * of HTML tag. The element given is representing
  127. * the tag and can be used to determine the nesting
  128. * for situations where the attributes will differ
  129. * if nesting inside of elements.
  130. *
  131. * @param t the type to translate to visual attributes.
  132. * @param e the element representing the tag. The element
  133. * can be used to determine the nesting for situations where
  134. * the attributes will differ if nested inside of other
  135. * elements.
  136. * @returns the set of CSS attributes to use to render
  137. * the tag.
  138. */
  139. public Style getRule(HTML.Tag t, Element e) {
  140. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  141. try {
  142. // Build an array of all the parent elements.
  143. Vector searchContext = sb.getVector();
  144. for (Element p = e; p != null; p = p.getParentElement()) {
  145. searchContext.addElement(p);
  146. }
  147. // Build a fully qualified selector.
  148. int n = searchContext.size();
  149. StringBuffer cacheLookup = sb.getStringBuffer();
  150. AttributeSet attr;
  151. String eName;
  152. Object name;
  153. // >= 1 as the HTML.Tag for the 0th element is passed in.
  154. for (int counter = n - 1; counter >= 1; counter--) {
  155. e = (Element)searchContext.elementAt(counter);
  156. attr = e.getAttributes();
  157. name = attr.getAttribute(StyleConstants.NameAttribute);
  158. eName = name.toString();
  159. cacheLookup.append(eName);
  160. if (attr != null) {
  161. if (attr.isDefined(HTML.Attribute.ID)) {
  162. cacheLookup.append('#');
  163. cacheLookup.append(attr.getAttribute
  164. (HTML.Attribute.ID));
  165. }
  166. else if (attr.isDefined(HTML.Attribute.CLASS)) {
  167. cacheLookup.append('.');
  168. cacheLookup.append(attr.getAttribute
  169. (HTML.Attribute.CLASS));
  170. }
  171. }
  172. cacheLookup.append(' ');
  173. }
  174. cacheLookup.append(t.toString());
  175. e = (Element)searchContext.elementAt(0);
  176. attr = e.getAttributes();
  177. if (e.isLeaf()) {
  178. // For leafs, we use the second tier attributes.
  179. Object testAttr = attr.getAttribute(t);
  180. if (testAttr instanceof AttributeSet) {
  181. attr = (AttributeSet)testAttr;
  182. }
  183. else {
  184. attr = null;
  185. }
  186. }
  187. if (attr != null) {
  188. if (attr.isDefined(HTML.Attribute.ID)) {
  189. cacheLookup.append('#');
  190. cacheLookup.append(attr.getAttribute(HTML.Attribute.ID));
  191. }
  192. else if (attr.isDefined(HTML.Attribute.CLASS)) {
  193. cacheLookup.append('.');
  194. cacheLookup.append(attr.getAttribute
  195. (HTML.Attribute.CLASS));
  196. }
  197. }
  198. Style style = getResolvedStyle(cacheLookup.toString(),
  199. searchContext, t);
  200. return style;
  201. }
  202. finally {
  203. SearchBuffer.releaseSearchBuffer(sb);
  204. }
  205. }
  206. /**
  207. * Fetches the rule that best matches the selector given
  208. * in string form. Where <code>selector</code> is a space separated
  209. * String of the element names. For example, <code>selector</code>
  210. * might be 'html body tr td''<p>
  211. * The attributes of the returned Style will change
  212. * as rules are added and removed. That is if you to ask for a rule
  213. * with a selector "table p" and a new rule was added with a selector
  214. * of "p" the returned Style would include the new attributes from
  215. * the rule "p".
  216. */
  217. public Style getRule(String selector) {
  218. selector = cleanSelectorString(selector);
  219. if (selector != null) {
  220. Style style = getResolvedStyle(selector);
  221. return style;
  222. }
  223. return null;
  224. }
  225. /**
  226. * Adds a set of rules to the sheet. The rules are expected to
  227. * be in valid CSS format. Typically this would be called as
  228. * a result of parsing a <style> tag.
  229. */
  230. public void addRule(String rule) {
  231. if (rule != null) {
  232. CssParser parser = new CssParser();
  233. try {
  234. parser.parse(getBase(), new StringReader(rule), false, false);
  235. } catch (IOException ioe) { }
  236. }
  237. }
  238. /**
  239. * Translates a CSS declaration to an AttributeSet that represents
  240. * the CSS declaration. Typically this would be called as a
  241. * result of encountering an HTML style attribute.
  242. */
  243. public AttributeSet getDeclaration(String decl) {
  244. if (decl == null) {
  245. return SimpleAttributeSet.EMPTY;
  246. }
  247. CssParser parser = new CssParser();
  248. return parser.parseDeclaration(decl);
  249. }
  250. /**
  251. * Loads a set of rules that have been specified in terms of
  252. * CSS1 grammar. If there are collisions with existing rules,
  253. * the newly specified rule will win.
  254. *
  255. * @param in the stream to read the CSS grammar from
  256. * @param ref the reference URL. This value represents the
  257. * location of the stream and may be null. All relative
  258. * URLs specified in the stream will be based upon this
  259. * parameter.
  260. */
  261. public void loadRules(Reader in, URL ref) throws IOException {
  262. CssParser parser = new CssParser();
  263. parser.parse(ref, in, false, false);
  264. }
  265. /**
  266. * Fetches a set of attributes to use in the view for
  267. * displaying. This is basically a set of attributes that
  268. * can be used for View.getAttributes.
  269. */
  270. public AttributeSet getViewAttributes(View v) {
  271. return new ViewAttributeSet(v);
  272. }
  273. /**
  274. * Removes a named style previously added to the document.
  275. *
  276. * @param nm the name of the style to remove
  277. */
  278. public void removeStyle(String nm) {
  279. Style aStyle = getStyle(nm);
  280. if (aStyle != null) {
  281. String selector = cleanSelectorString(nm);
  282. String[] selectors = getSimpleSelectors(selector);
  283. synchronized(this) {
  284. Object mapping = getRootSelectorMapping();
  285. for (int i = selectors.length - 1; i >= 0; i--) {
  286. mapping = getSelectorMapping(mapping, selectors[i], true);
  287. }
  288. Style rule = getMappingStyle(mapping);
  289. if (rule != null) {
  290. removeMappingStyle(mapping);
  291. if (resolvedStyles.size() > 0) {
  292. Enumeration values = resolvedStyles.elements();
  293. while (values.hasMoreElements()) {
  294. ResolvedStyle style = (ResolvedStyle)values.
  295. nextElement();
  296. style.removeStyle(rule);
  297. }
  298. }
  299. }
  300. }
  301. }
  302. super.removeStyle(nm);
  303. }
  304. /**
  305. * Adds the rules from the StyleSheet <code>ss</code> to those of
  306. * the receiver. <code>ss's</code> rules will override the rules of
  307. * any previously added style sheets. An added StyleSheet will never
  308. * override the rules of the receiving style sheet.
  309. *
  310. * @since 1.3
  311. */
  312. public void addStyleSheet(StyleSheet ss) {
  313. synchronized(this) {
  314. if (linkedStyleSheets == null) {
  315. linkedStyleSheets = new Vector();
  316. }
  317. if (!linkedStyleSheets.contains(ss)) {
  318. linkedStyleSheets.insertElementAt(ss, 0);
  319. linkStyleSheetAt(ss, 0);
  320. }
  321. }
  322. }
  323. /**
  324. * Removes the StyleSheet <code>ss</code> from those of the receiver.
  325. *
  326. * @since 1.3
  327. */
  328. public void removeStyleSheet(StyleSheet ss) {
  329. synchronized(this) {
  330. if (linkedStyleSheets != null) {
  331. int index = linkedStyleSheets.indexOf(ss);
  332. if (index != -1) {
  333. linkedStyleSheets.removeElementAt(index);
  334. unlinkStyleSheet(ss, index);
  335. if (index == 0 && linkedStyleSheets.size() == 0) {
  336. linkedStyleSheets = null;
  337. }
  338. }
  339. }
  340. }
  341. }
  342. //
  343. // The following is used to import style sheets.
  344. //
  345. /**
  346. * Returns an array of the linked StyleSheets. Will return null
  347. * if there are no linked StyleSheets.
  348. *
  349. * @since 1.3
  350. */
  351. public StyleSheet[] getStyleSheets() {
  352. StyleSheet[] retValue;
  353. synchronized(this) {
  354. if (linkedStyleSheets != null) {
  355. retValue = new StyleSheet[linkedStyleSheets.size()];
  356. linkedStyleSheets.copyInto(retValue);
  357. }
  358. else {
  359. retValue = null;
  360. }
  361. }
  362. return retValue;
  363. }
  364. /**
  365. * Imports a style sheet from <code>url</code>. The resulting rules
  366. * are directly added to the receiver. If you do not want the rules
  367. * to become part of the receiver, create a new StyleSheet and use
  368. * addStyleSheet to link it in.
  369. *
  370. * @since 1.3
  371. */
  372. public void importStyleSheet(URL url) {
  373. try {
  374. InputStream is;
  375. is = url.openStream();
  376. Reader r = new BufferedReader(new InputStreamReader(is));
  377. CssParser parser = new CssParser();
  378. parser.parse(url, r, false, true);
  379. r.close();
  380. is.close();
  381. } catch (Throwable e) {
  382. // on error we simply have no styles... the html
  383. // will look mighty wrong but still function.
  384. }
  385. }
  386. /**
  387. * Sets the base. All import statements that are relative, will be
  388. * relative to <code>base</code>.
  389. *
  390. * @since 1.3
  391. */
  392. public void setBase(URL base) {
  393. this.base = base;
  394. }
  395. /**
  396. * Returns the base.
  397. *
  398. * @since 1.3
  399. */
  400. public URL getBase() {
  401. return base;
  402. }
  403. /**
  404. * Adds a CSS attribute to the given set.
  405. *
  406. * @since 1.3
  407. */
  408. public void addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key,
  409. String value) {
  410. css.addInternalCSSValue(attr, key, value);
  411. }
  412. /**
  413. * Adds a CSS attribute to the given set.
  414. *
  415. * @since 1.3
  416. */
  417. public boolean addCSSAttributeFromHTML(MutableAttributeSet attr,
  418. CSS.Attribute key, String value) {
  419. Object iValue = css.getCssValue(key, value);
  420. if (iValue != null) {
  421. attr.addAttribute(key, iValue);
  422. return true;
  423. }
  424. return false;
  425. }
  426. // ---- Conversion functionality ---------------------------------
  427. /**
  428. * Converts a set of HTML attributes to an equivalent
  429. * set of CSS attributes.
  430. *
  431. * @param AttributeSet containing the HTML attributes.
  432. * @param AttributeSet containing the corresponding CSS attributes.
  433. * The AttributeSet will be empty if there are no mapping
  434. * CSS attributes.
  435. */
  436. public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
  437. AttributeSet cssAttrSet = css.translateHTMLToCSS(htmlAttrSet);
  438. MutableAttributeSet cssStyleSet = addStyle(null, null);
  439. cssStyleSet.addAttributes(cssAttrSet);
  440. return cssStyleSet;
  441. }
  442. /**
  443. * Adds an attribute to the given set, and returns
  444. * the new representative set. This is reimplemented to
  445. * convert StyleConstant attributes to CSS prior to forwarding
  446. * to the superclass behavior. The StyleConstants attribute
  447. * has no corresponding CSS entry, the StyleConstants attribute
  448. * is stored (but will likely be unused).
  449. *
  450. * @param old the old attribute set
  451. * @param key the non-null attribute key
  452. * @param value the attribute value
  453. * @return the updated attribute set
  454. * @see MutableAttributeSet#addAttribute
  455. */
  456. public AttributeSet addAttribute(AttributeSet old, Object key,
  457. Object value) {
  458. if (css == null) {
  459. // supers constructor will call this before returning,
  460. // and we need to make sure CSS is non null.
  461. css = new CSS();
  462. }
  463. if (key instanceof StyleConstants) {
  464. Object cssValue = css.styleConstantsValueToCSSValue
  465. ((StyleConstants)key, value);
  466. if (cssValue != null) {
  467. Object cssKey = css.styleConstantsKeyToCSSKey
  468. ((StyleConstants)key);
  469. if (cssKey != null) {
  470. return super.addAttribute(old, cssKey, cssValue);
  471. }
  472. }
  473. }
  474. return super.addAttribute(old, key, value);
  475. }
  476. /**
  477. * Adds a set of attributes to the element. If any of these attributes
  478. * are StyleConstants attributes, they will be converted to CSS prior
  479. * to forwarding to the superclass behavior.
  480. *
  481. * @param old the old attribute set
  482. * @param attr the attributes to add
  483. * @return the updated attribute set
  484. * @see MutableAttributeSet#addAttribute
  485. */
  486. public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) {
  487. return super.addAttributes(old, convertAttributeSet(attr));
  488. }
  489. /**
  490. * Removes an attribute from the set. If the attribute is a StyleConstants
  491. * attribute, the request will be converted to a CSS attribute prior to
  492. * forwarding to the superclass behavior.
  493. *
  494. * @param old the old set of attributes
  495. * @param key the non-null attribute name
  496. * @return the updated attribute set
  497. * @see MutableAttributeSet#removeAttribute
  498. */
  499. public AttributeSet removeAttribute(AttributeSet old, Object key) {
  500. if (key instanceof StyleConstants) {
  501. Object cssKey = css.styleConstantsKeyToCSSKey((StyleConstants)key);
  502. if (cssKey != null) {
  503. return super.removeAttribute(old, cssKey);
  504. }
  505. }
  506. return super.removeAttribute(old, key);
  507. }
  508. /**
  509. * Removes a set of attributes for the element. If any of the attributes
  510. * is a StyleConstants attribute, the request will be converted to a CSS
  511. * attribute prior to forwarding to the superclass behavior.
  512. *
  513. * @param old the old attribute set
  514. * @param names the attribute names
  515. * @return the updated attribute set
  516. * @see MutableAttributeSet#removeAttributes
  517. */
  518. public AttributeSet removeAttributes(AttributeSet old, Enumeration names) {
  519. return super.removeAttributes(old, names);
  520. }
  521. /**
  522. * Removes a set of attributes. If any of the attributes
  523. * is a StyleConstants attribute, the request will be converted to a CSS
  524. * attribute prior to forwarding to the superclass behavior.
  525. *
  526. * @param old the old attribute set
  527. * @param attrs the attributes
  528. * @return the updated attribute set
  529. * @see MutableAttributeSet#removeAttributes
  530. */
  531. public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) {
  532. return super.removeAttributes(old, convertAttributeSet(attrs));
  533. }
  534. /**
  535. * Creates a compact set of attributes that might be shared.
  536. * This is a hook for subclasses that want to alter the
  537. * behavior of SmallAttributeSet. This can be reimplemented
  538. * to return an AttributeSet that provides some sort of
  539. * attribute conversion.
  540. *
  541. * @param a The set of attributes to be represented in the
  542. * the compact form.
  543. */
  544. protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) {
  545. return new SmallConversionSet(a);
  546. }
  547. /**
  548. * Creates a large set of attributes that should trade off
  549. * space for time. This set will not be shared. This is
  550. * a hook for subclasses that want to alter the behavior
  551. * of the larger attribute storage format (which is
  552. * SimpleAttributeSet by default). This can be reimplemented
  553. * to return a MutableAttributeSet that provides some sort of
  554. * attribute conversion.
  555. *
  556. * @param a The set of attributes to be represented in the
  557. * the larger form.
  558. */
  559. protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) {
  560. return new LargeConversionSet(a);
  561. }
  562. /**
  563. * Converts a set of attributes (if necessary) so that
  564. * any attributes that were specified as StyleConstants
  565. * attributes and have a CSS mapping, will be converted
  566. * to CSS attributes.
  567. */
  568. AttributeSet convertAttributeSet(AttributeSet a) {
  569. if ((a instanceof LargeConversionSet) ||
  570. (a instanceof SmallConversionSet)) {
  571. // known to be converted.
  572. return a;
  573. }
  574. // in most cases, there are no StyleConstants attributes
  575. // so we iterate the collection of keys to avoid creating
  576. // a new set.
  577. Enumeration names = a.getAttributeNames();
  578. while (names.hasMoreElements()) {
  579. Object name = names.nextElement();
  580. if (name instanceof StyleConstants) {
  581. // we really need to do a conversion, iterate again
  582. // building a new set.
  583. MutableAttributeSet converted = new LargeConversionSet();
  584. Enumeration keys = a.getAttributeNames();
  585. while (keys.hasMoreElements()) {
  586. Object key = keys.nextElement();
  587. Object cssValue = null;
  588. if (key instanceof StyleConstants) {
  589. // convert the StyleConstants attribute if possible
  590. Object cssKey = css.styleConstantsKeyToCSSKey
  591. ((StyleConstants)key);
  592. if (cssKey != null) {
  593. Object value = a.getAttribute(key);
  594. cssValue = css.styleConstantsValueToCSSValue
  595. ((StyleConstants)key, value);
  596. if (cssValue != null) {
  597. converted.addAttribute(cssKey, cssValue);
  598. }
  599. }
  600. }
  601. if (cssValue == null) {
  602. converted.addAttribute(key, a.getAttribute(key));
  603. }
  604. }
  605. return converted;
  606. }
  607. }
  608. return a;
  609. }
  610. /**
  611. * Large set of attributes that does conversion of requests
  612. * for attributes of type StyleConstants.
  613. */
  614. class LargeConversionSet extends SimpleAttributeSet {
  615. /**
  616. * Creates a new attribute set based on a supplied set of attributes.
  617. *
  618. * @param source the set of attributes
  619. */
  620. public LargeConversionSet(AttributeSet source) {
  621. super(source);
  622. }
  623. public LargeConversionSet() {
  624. super();
  625. }
  626. /**
  627. * Checks whether a given attribute is defined.
  628. *
  629. * @param key the attribute key
  630. * @return true if the attribute is defined
  631. * @see AttributeSet#isDefined
  632. */
  633. public boolean isDefined(Object key) {
  634. if (key instanceof StyleConstants) {
  635. Object cssKey = css.styleConstantsKeyToCSSKey
  636. ((StyleConstants)key);
  637. if (cssKey != null) {
  638. return super.isDefined(cssKey);
  639. }
  640. }
  641. return super.isDefined(key);
  642. }
  643. /**
  644. * Gets the value of an attribute.
  645. *
  646. * @param key the attribute name
  647. * @return the attribute value
  648. * @see AttributeSet#getAttribute
  649. */
  650. public Object getAttribute(Object key) {
  651. if (key instanceof StyleConstants) {
  652. Object cssKey = css.styleConstantsKeyToCSSKey
  653. ((StyleConstants)key);
  654. if (cssKey != null) {
  655. Object value = super.getAttribute(cssKey);
  656. if (value != null) {
  657. return css.cssValueToStyleConstantsValue
  658. ((StyleConstants)key, value);
  659. }
  660. }
  661. }
  662. return super.getAttribute(key);
  663. }
  664. }
  665. /**
  666. * Small set of attributes that does conversion of requests
  667. * for attributes of type StyleConstants.
  668. */
  669. class SmallConversionSet extends SmallAttributeSet {
  670. /**
  671. * Creates a new attribute set based on a supplied set of attributes.
  672. *
  673. * @param source the set of attributes
  674. */
  675. public SmallConversionSet(AttributeSet attrs) {
  676. super(attrs);
  677. }
  678. /**
  679. * Checks whether a given attribute is defined.
  680. *
  681. * @param key the attribute key
  682. * @return true if the attribute is defined
  683. * @see AttributeSet#isDefined
  684. */
  685. public boolean isDefined(Object key) {
  686. if (key instanceof StyleConstants) {
  687. Object cssKey = css.styleConstantsKeyToCSSKey
  688. ((StyleConstants)key);
  689. if (cssKey != null) {
  690. return super.isDefined(cssKey);
  691. }
  692. }
  693. return super.isDefined(key);
  694. }
  695. /**
  696. * Gets the value of an attribute.
  697. *
  698. * @param key the attribute name
  699. * @return the attribute value
  700. * @see AttributeSet#getAttribute
  701. */
  702. public Object getAttribute(Object key) {
  703. if (key instanceof StyleConstants) {
  704. Object cssKey = css.styleConstantsKeyToCSSKey
  705. ((StyleConstants)key);
  706. if (cssKey != null) {
  707. Object value = super.getAttribute(cssKey);
  708. if (value != null) {
  709. return css.cssValueToStyleConstantsValue
  710. ((StyleConstants)key, value);
  711. }
  712. }
  713. }
  714. return super.getAttribute(key);
  715. }
  716. }
  717. // ---- Resource handling ----------------------------------------
  718. /**
  719. * Fetches the font to use for the given set of attributes.
  720. */
  721. public Font getFont(AttributeSet a) {
  722. return css.getFont(this, a, 12);
  723. }
  724. /**
  725. * Takes a set of attributes and turn it into a foreground color
  726. * specification. This might be used to specify things
  727. * like brighter, more hue, etc.
  728. *
  729. * @param a the set of attributes
  730. * @return the color
  731. */
  732. public Color getForeground(AttributeSet a) {
  733. Color c = css.getColor(a, CSS.Attribute.COLOR);
  734. if (c == null) {
  735. return Color.black;
  736. }
  737. return c;
  738. }
  739. /**
  740. * Takes a set of attributes and turn it into a background color
  741. * specification. This might be used to specify things
  742. * like brighter, more hue, etc.
  743. *
  744. * @param attr the set of attributes
  745. * @return the color
  746. */
  747. public Color getBackground(AttributeSet a) {
  748. return css.getColor(a, CSS.Attribute.BACKGROUND_COLOR);
  749. }
  750. /**
  751. * Fetches the box formatter to use for the given set
  752. * of CSS attributes.
  753. */
  754. public BoxPainter getBoxPainter(AttributeSet a) {
  755. return new BoxPainter(a, css, this);
  756. }
  757. /**
  758. * Fetches the list formatter to use for the given set
  759. * of CSS attributes.
  760. */
  761. public ListPainter getListPainter(AttributeSet a) {
  762. return new ListPainter(a, this);
  763. }
  764. public void setBaseFontSize(int sz) {
  765. css.setBaseFontSize(sz);
  766. }
  767. public void setBaseFontSize(String size) {
  768. css.setBaseFontSize(size);
  769. }
  770. public static int getIndexOfSize(float pt) {
  771. return CSS.getIndexOfSize(pt);
  772. }
  773. /**
  774. * Returns the point size, given a size index.
  775. */
  776. public float getPointSize(int index) {
  777. return css.getPointSize(index);
  778. }
  779. /**
  780. * Given a string such as "+2", "-2", or "2",
  781. * returns a point size value.
  782. */
  783. public float getPointSize(String size) {
  784. return css.getPointSize(size);
  785. }
  786. /**
  787. * Converts a color string such as "RED" or "#NNNNNN" to a Color.
  788. * Note: This will only convert the HTML3.2 color strings
  789. * or a string of length 7;
  790. * otherwise, it will return null.
  791. */
  792. public Color stringToColor(String string) {
  793. return CSS.stringToColor(string);
  794. }
  795. /**
  796. * Returns the ImageIcon to draw in the background for
  797. * <code>attr</code>.
  798. */
  799. ImageIcon getBackgroundImage(AttributeSet attr) {
  800. Object value = attr.getAttribute(CSS.Attribute.BACKGROUND_IMAGE);
  801. if (value != null) {
  802. return ((CSS.BackgroundImage)value).getImage(getBase());
  803. }
  804. return null;
  805. }
  806. /**
  807. * Adds a rule into the StyleSheet.
  808. *
  809. * @param selector the selector to use for the rule.
  810. * This will be a set of simple selectors, and must
  811. * be a length of 1 or greater.
  812. * @param declaration the set of CSS attributes that
  813. * make up the rule.
  814. */
  815. void addRule(String[] selector, AttributeSet declaration,
  816. boolean isLinked) {
  817. int n = selector.length;
  818. StringBuffer sb = new StringBuffer();
  819. sb.append(selector[0]);
  820. for (int counter = 1; counter < n; counter++) {
  821. sb.append(' ');
  822. sb.append(selector[counter]);
  823. }
  824. String selectorName = sb.toString();
  825. Style rule = getStyle(selectorName);
  826. if (rule == null) {
  827. // Notice how the rule is first created, and it not part of
  828. // the synchronized block. It is done like this as creating
  829. // a new rule will fire a ChangeEvent. We do not want to be
  830. // holding the lock when calling to other objects, it can
  831. // result in deadlock.
  832. Style altRule = addStyle(selectorName, null);
  833. synchronized(this) {
  834. Object mapping = getRootSelectorMapping();
  835. for (int i = n - 1; i >= 0; i--) {
  836. mapping = getSelectorMapping(mapping, selector[i], true);
  837. }
  838. rule = getMappingStyle(mapping);
  839. if (rule == null) {
  840. rule = createStyleForSelector(selectorName, mapping,
  841. altRule);
  842. refreshResolvedRules(selectorName, selector, rule,
  843. getSpecificity(mapping));
  844. }
  845. }
  846. }
  847. if (isLinked) {
  848. rule = getLinkedStyle(rule);
  849. }
  850. rule.addAttributes(declaration);
  851. }
  852. //
  853. // The following gaggle of methods is used in maintaing the rules from
  854. // the sheet.
  855. //
  856. /**
  857. * Updates the attributes of the rules to reference any related
  858. * rules in <code>ss</code>.
  859. */
  860. private synchronized void linkStyleSheetAt(StyleSheet ss, int index) {
  861. if (resolvedStyles.size() > 0) {
  862. Enumeration values = resolvedStyles.elements();
  863. while (values.hasMoreElements()) {
  864. ResolvedStyle rule = (ResolvedStyle)values.nextElement();
  865. rule.insertExtendedStyleAt(ss.getRule(rule.getName()),
  866. index);
  867. }
  868. }
  869. }
  870. /**
  871. * Removes references to the rules in <code>ss</code>.
  872. * <code>index</code> gives the index the StyleSheet was at, that is
  873. * how many StyleSheets had been added before it.
  874. */
  875. private synchronized void unlinkStyleSheet(StyleSheet ss, int index) {
  876. if (resolvedStyles.size() > 0) {
  877. Enumeration values = resolvedStyles.elements();
  878. while (values.hasMoreElements()) {
  879. ResolvedStyle rule = (ResolvedStyle)values.nextElement();
  880. rule.removeExtendedStyleAt(index);
  881. }
  882. }
  883. }
  884. /**
  885. * Returns the simple selectors that comprise selector.
  886. */
  887. /* protected */
  888. String[] getSimpleSelectors(String selector) {
  889. selector = cleanSelectorString(selector);
  890. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  891. Vector selectors = sb.getVector();
  892. int lastIndex = 0;
  893. int length = selector.length();
  894. while (lastIndex != -1) {
  895. int newIndex = selector.indexOf(' ', lastIndex);
  896. if (newIndex != -1) {
  897. selectors.addElement(selector.substring(lastIndex, newIndex));
  898. if (++newIndex == length) {
  899. lastIndex = -1;
  900. }
  901. else {
  902. lastIndex = newIndex;
  903. }
  904. }
  905. else {
  906. selectors.addElement(selector.substring(lastIndex));
  907. lastIndex = -1;
  908. }
  909. }
  910. String[] retValue = new String[selectors.size()];
  911. selectors.copyInto(retValue);
  912. SearchBuffer.releaseSearchBuffer(sb);
  913. return retValue;
  914. }
  915. /**
  916. * Returns a string that only has one space between simple selectors,
  917. * which may be the passed in String.
  918. */
  919. /*protected*/ String cleanSelectorString(String selector) {
  920. boolean lastWasSpace = true;
  921. for (int counter = 0, maxCounter = selector.length();
  922. counter < maxCounter; counter++) {
  923. switch(selector.charAt(counter)) {
  924. case ' ':
  925. if (lastWasSpace) {
  926. return _cleanSelectorString(selector);
  927. }
  928. lastWasSpace = true;
  929. break;
  930. case '\n':
  931. case '\r':
  932. case '\t':
  933. return _cleanSelectorString(selector);
  934. default:
  935. lastWasSpace = false;
  936. }
  937. }
  938. if (lastWasSpace) {
  939. return _cleanSelectorString(selector);
  940. }
  941. // It was fine.
  942. return selector;
  943. }
  944. /**
  945. * Returns a new String that contains only one space between non
  946. * white space characters.
  947. */
  948. private String _cleanSelectorString(String selector) {
  949. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  950. StringBuffer buff = sb.getStringBuffer();
  951. boolean lastWasSpace = true;
  952. int lastIndex = 0;
  953. char[] chars = selector.toCharArray();
  954. int numChars = chars.length;
  955. String retValue = null;
  956. try {
  957. for (int counter = 0; counter < numChars; counter++) {
  958. switch(chars[counter]) {
  959. case ' ':
  960. if (!lastWasSpace) {
  961. lastWasSpace = true;
  962. if (lastIndex < counter) {
  963. buff.append(chars, lastIndex,
  964. 1 + counter - lastIndex);
  965. }
  966. }
  967. lastIndex = counter + 1;
  968. break;
  969. case '\n':
  970. case '\r':
  971. case '\t':
  972. if (!lastWasSpace) {
  973. lastWasSpace = true;
  974. if (lastIndex < counter) {
  975. buff.append(chars, lastIndex,
  976. counter - lastIndex);
  977. buff.append(' ');
  978. }
  979. }
  980. lastIndex = counter + 1;
  981. break;
  982. default:
  983. lastWasSpace = false;
  984. break;
  985. }
  986. }
  987. if (lastWasSpace && buff.length() > 0) {
  988. // Remove last space.
  989. buff.setLength(buff.length() - 1);
  990. }
  991. else if (lastIndex < numChars) {
  992. buff.append(chars, lastIndex, numChars - lastIndex);
  993. }
  994. retValue = buff.toString();
  995. }
  996. finally {
  997. SearchBuffer.releaseSearchBuffer(sb);
  998. }
  999. return retValue;
  1000. }
  1001. /**
  1002. * Returns the root selector mapping that all selectors are relative
  1003. * too. This is an inverted graph of the selectors.
  1004. */
  1005. private Object getRootSelectorMapping() {
  1006. return selectorMapping;
  1007. }
  1008. /**
  1009. * Returns the child mapping of <code>parent</code> for
  1010. * <code>selector</code>. If there is no mapping for <code>selector</code>
  1011. * and <code>create</code> is false, this will return null.
  1012. */
  1013. private synchronized Object getSelectorMapping(Object parent,
  1014. String selector,
  1015. boolean create) {
  1016. Hashtable retValue = (Hashtable)((Hashtable)parent).get(selector);
  1017. if (retValue == null && create) {
  1018. retValue = new Hashtable(7);
  1019. ((Hashtable)parent).put(selector, retValue);
  1020. // Update specificity for child.
  1021. int specificity = 0;
  1022. if (parent != null) {
  1023. Object pSpec = ((Hashtable)parent).get(SPECIFICITY);
  1024. if (pSpec != null) {
  1025. specificity = ((Integer)pSpec).intValue();
  1026. }
  1027. }
  1028. // class (.) 100
  1029. // id (#) 10000
  1030. char firstChar = selector.charAt(0);
  1031. if (firstChar == '.') {
  1032. specificity += 100;
  1033. }
  1034. else if (firstChar == '#') {
  1035. specificity += 10000;
  1036. }
  1037. else {
  1038. specificity += 1;
  1039. if (selector.indexOf('.') != -1) {
  1040. specificity += 100;
  1041. }
  1042. if (selector.indexOf('#') != -1) {
  1043. specificity += 10000;
  1044. }
  1045. }
  1046. retValue.put(SPECIFICITY, new Integer(specificity));
  1047. }
  1048. return retValue;
  1049. }
  1050. /**
  1051. * Returns the specificity of the passed in String. It assumes the
  1052. * passed in string doesn't contain junk, that is each selector is
  1053. * separated by a space and each selector at most contains one . or one
  1054. * #. A simple selector has a weight of 1, an id selector has a weight
  1055. * of 100, and a class selector has a weight of 10000.
  1056. */
  1057. /*protected*/ static int getSpecificity(String selector) {
  1058. int specificity = 0;
  1059. boolean lastWasSpace = true;
  1060. for (int counter = 0, maxCounter = selector.length();
  1061. counter < maxCounter; counter++) {
  1062. switch(selector.charAt(counter)) {
  1063. case '.':
  1064. specificity += 100;
  1065. break;
  1066. case '#':
  1067. specificity += 10000;
  1068. break;
  1069. case ' ':
  1070. lastWasSpace = true;
  1071. break;
  1072. default:
  1073. if (lastWasSpace) {
  1074. lastWasSpace = false;
  1075. specificity += 1;
  1076. }
  1077. }
  1078. }
  1079. return specificity;
  1080. }
  1081. /**
  1082. * Returns the specificity of the passed in mapping.
  1083. */
  1084. private int getSpecificity(Object mapping) {
  1085. Object pSpec = ((Hashtable)mapping).get(SPECIFICITY);
  1086. if (pSpec != null) {
  1087. return ((Integer)pSpec).intValue();
  1088. }
  1089. return 0;
  1090. }
  1091. /**
  1092. * Returns the style for the passed in mapping.
  1093. */
  1094. private Style getMappingStyle(Object mapping) {
  1095. return (Style)((Hashtable)mapping).get(RULE);
  1096. }
  1097. /**
  1098. * Removes the previously added mapping style.
  1099. */
  1100. private void removeMappingStyle(Object mapping) {
  1101. ((Hashtable)mapping).remove(RULE);
  1102. }
  1103. /**
  1104. * Returns the style that linked attributes should be added to. This
  1105. * will create the style if necessary.
  1106. */
  1107. private Style getLinkedStyle(Style localStyle) {
  1108. // NOTE: This is not synchronized, and the caller of this does
  1109. // not synchronize. There is the chance for one of the callers to
  1110. // overwrite the existing resolved parent, but it is quite rare.
  1111. // The reason this is left like this is because setResolveParent
  1112. // will fire a ChangeEvent. It is really, REALLY bad for us to
  1113. // hold a lock when calling outside of us, it may cause a deadlock.
  1114. Style retStyle = (Style)localStyle.getResolveParent();
  1115. if (retStyle == null) {
  1116. retStyle = addStyle(null, null);
  1117. localStyle.setResolveParent(retStyle);
  1118. }
  1119. return retStyle;
  1120. }
  1121. /**
  1122. * Returns the Style appropriate for <code>selector</code> and
  1123. * <code>mapping</code>. If a Style does not currently exist,
  1124. * <code>altStyle</code> will be used.
  1125. */
  1126. private synchronized Style createStyleForSelector(String selector,
  1127. Object mapping,
  1128. Style altStyle) {
  1129. Style style = (Style)((Hashtable)mapping).get(RULE);
  1130. if (style == null) {
  1131. style = altStyle;
  1132. ((Hashtable)mapping).put(RULE, altStyle);
  1133. }
  1134. return style;
  1135. }
  1136. /**
  1137. * Returns the resolved style for <code>selector</code>. This will
  1138. * create the resolved style, if necessary.
  1139. */
  1140. private synchronized Style getResolvedStyle(String selector,
  1141. Vector elements,
  1142. HTML.Tag t) {
  1143. Style retStyle = (Style)resolvedStyles.get(selector);
  1144. if (retStyle == null) {
  1145. retStyle = createResolvedStyle(selector, elements, t);
  1146. }
  1147. return retStyle;
  1148. }
  1149. /**
  1150. * Returns the resolved style for <code>selector</code>. This will
  1151. * create the resolved style, if necessary.
  1152. */
  1153. private synchronized Style getResolvedStyle(String selector) {
  1154. Style retStyle = (Style)resolvedStyles.get(selector);
  1155. if (retStyle == null) {
  1156. retStyle = createResolvedStyle(selector);
  1157. }
  1158. return retStyle;
  1159. }
  1160. /**
  1161. * Adds <code>mapping</code> to <code>elements</code>. It is added
  1162. * such that <code>elements</code> will remain ordered by
  1163. * specificity.
  1164. */
  1165. private void addSortedStyle(Object mapping, Vector elements) {
  1166. int size = elements.size();
  1167. if (size > 0) {
  1168. int specificity = getSpecificity(mapping);
  1169. for (int counter = 0; counter < size; counter++) {
  1170. if (specificity >= getSpecificity(elements.
  1171. elementAt(counter))) {
  1172. elements.insertElementAt(mapping, counter);
  1173. return;
  1174. }
  1175. }
  1176. }
  1177. elements.addElement(mapping);
  1178. }
  1179. /**
  1180. * Adds <code>parentMapping</code> to <code>styles</code>, and
  1181. * recursively calls this method if <code>parentMapping</code> has
  1182. * any child mappings for any of the Elements in <code>elements</code>.
  1183. */
  1184. private synchronized void getStyles(Object parentMapping,
  1185. Vector styles,
  1186. String[] tags, String[] ids, String[] classes,
  1187. int index, int numElements,
  1188. Hashtable alreadyChecked) {
  1189. // Avoid desending the same mapping twice.
  1190. if (alreadyChecked.contains(parentMapping)) {
  1191. return;
  1192. }
  1193. alreadyChecked.put(parentMapping, parentMapping);
  1194. Style style = getMappingStyle(parentMapping);
  1195. if (style != null) {
  1196. addSortedStyle(parentMapping, styles);
  1197. }
  1198. for (int counter = index; counter < numElements; counter++) {
  1199. String tagString = tags[counter];
  1200. if (tagString != null) {
  1201. Object childMapping = getSelectorMapping(parentMapping,
  1202. tagString, false);
  1203. if (childMapping != null) {
  1204. getStyles(childMapping, styles, tags, ids, classes,
  1205. counter + 1, numElements, alreadyChecked);
  1206. }
  1207. if (classes[counter] != null) {
  1208. String className = classes[counter];
  1209. childMapping = getSelectorMapping(parentMapping,
  1210. tagString + "." + className, false);
  1211. if (childMapping != null) {
  1212. getStyles(childMapping, styles, tags, ids, classes,
  1213. counter + 1, numElements, alreadyChecked);
  1214. }
  1215. childMapping = getSelectorMapping(parentMapping, "." +
  1216. className, false);
  1217. if (childMapping != null) {
  1218. getStyles(childMapping, styles, tags, ids, classes,
  1219. counter + 1, numElements, alreadyChecked);
  1220. }
  1221. }
  1222. if (ids[counter] != null) {
  1223. String idName = ids[counter];
  1224. childMapping = getSelectorMapping(parentMapping,
  1225. tagString + "#" + idName, false);
  1226. if (childMapping != null) {
  1227. getStyles(childMapping, styles, tags, ids, classes,
  1228. counter + 1, numElements, alreadyChecked);
  1229. }
  1230. childMapping = getSelectorMapping(parentMapping, "#" +
  1231. idName, false);
  1232. if (childMapping != null) {
  1233. getStyles(childMapping, styles, tags, ids, classes,
  1234. counter + 1, numElements, alreadyChecked);
  1235. }
  1236. }
  1237. }
  1238. }
  1239. }
  1240. /**
  1241. * Creates and returns a Style containing all the rules that match
  1242. * <code>selector</code>.
  1243. */
  1244. private synchronized Style createResolvedStyle(String selector,
  1245. String[] tags,
  1246. String[] ids, String[] classes) {
  1247. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  1248. Vector tempVector = sb.getVector();
  1249. Hashtable tempHashtable = sb.getHashtable();
  1250. // Determine all the Styles that are appropriate, placing them
  1251. // in tempVector
  1252. try {
  1253. Object mapping = getRootSelectorMapping();
  1254. int numElements = tags.length;
  1255. String tagString = tags[0];
  1256. Object childMapping = getSelectorMapping(mapping, tagString,
  1257. false);
  1258. if (childMapping != null) {
  1259. getStyles(childMapping, tempVector, tags, ids, classes, 1,
  1260. numElements, tempHashtable);
  1261. }
  1262. if (classes[0] != null) {
  1263. String className = classes[0];
  1264. childMapping = getSelectorMapping(mapping, tagString + "." +
  1265. className, false);
  1266. if (childMapping != null) {
  1267. getStyles(childMapping, tempVector, tags, ids, classes, 1,
  1268. numElements, tempHashtable);
  1269. }
  1270. childMapping = getSelectorMapping(mapping, "." + className,
  1271. false);
  1272. if (childMapping != null) {
  1273. getStyles(childMapping, tempVector, tags, ids, classes,
  1274. 1, numElements, tempHashtable);
  1275. }
  1276. }
  1277. if (ids[0] != null) {
  1278. String idName = ids[0];
  1279. childMapping = getSelectorMapping(mapping, tagString + "#" +
  1280. idName, false);
  1281. if (childMapping != null) {
  1282. getStyles(childMapping, tempVector, tags, ids, classes,
  1283. 1, numElements, tempHashtable);
  1284. }
  1285. childMapping = getSelectorMapping(mapping, "#" + idName,
  1286. false);
  1287. if (childMapping != null) {
  1288. getStyles(childMapping, tempVector, tags, ids, classes,
  1289. 1, numElements, tempHashtable);
  1290. }
  1291. }
  1292. // Create a new Style that will delegate to all the matching
  1293. // Styles.
  1294. int numLinkedSS = (linkedStyleSheets != null) ?
  1295. linkedStyleSheets.size() : 0;
  1296. int numStyles = tempVector.size();
  1297. AttributeSet[] attrs = new AttributeSet[numStyles + numLinkedSS];
  1298. for (int counter = 0; counter < numStyles; counter++) {
  1299. attrs[counter] = getMappingStyle(tempVector.
  1300. elementAt(counter));
  1301. }
  1302. // Get the AttributeSet from linked style sheets.
  1303. for (int counter = 0; counter < numLinkedSS; counter++) {
  1304. AttributeSet attr = ((StyleSheet)linkedStyleSheets.
  1305. elementAt(counter)).getRule(selector);
  1306. if (attr == null) {
  1307. attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
  1308. }
  1309. else {
  1310. attrs[counter + numStyles] = attr;
  1311. }
  1312. }
  1313. ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
  1314. numStyles);
  1315. resolvedStyles.put(selector, retStyle);
  1316. return retStyle;
  1317. }
  1318. finally {
  1319. SearchBuffer.releaseSearchBuffer(sb);
  1320. }
  1321. }
  1322. /**
  1323. * Creates and returns a Style containing all the rules that
  1324. * matches <code>selector</code>.
  1325. *
  1326. * @param elements a Vector of all the Elements
  1327. * the style is being asked for. The
  1328. * first Element is the deepest Element, with the last Element
  1329. * representing the root.
  1330. * @param t the Tag to use for
  1331. * the first Element in <code>elements</code>
  1332. */
  1333. private Style createResolvedStyle(String selector, Vector elements,
  1334. HTML.Tag t) {
  1335. int numElements = elements.size();
  1336. // Build three arrays, one for tags, one for class's, and one for
  1337. // id's
  1338. String tags[] = new String[numElements];
  1339. String ids[] = new String[numElements];
  1340. String classes[] = new String[numElements];
  1341. for (int counter = 0; counter < numElements; counter++) {
  1342. Element e = (Element)elements.elementAt(counter);
  1343. AttributeSet attr = e.getAttributes();
  1344. if (counter == 0 && e.isLeaf()) {
  1345. // For leafs, we use the second tier attributes.
  1346. Object testAttr = attr.getAttribute(t);
  1347. if (testAttr instanceof AttributeSet) {
  1348. attr = (AttributeSet)testAttr;
  1349. }
  1350. else {
  1351. attr = null;
  1352. }
  1353. }
  1354. if (attr != null) {
  1355. HTML.Tag tag = (HTML.Tag)attr.getAttribute(StyleConstants.
  1356. NameAttribute);
  1357. if (tag != null) {
  1358. tags[counter] = tag.toString();
  1359. }
  1360. else {
  1361. tags[counter] = null;
  1362. }
  1363. if (attr.isDefined(HTML.Attribute.CLASS)) {
  1364. classes[counter] = attr.getAttribute
  1365. (HTML.Attribute.CLASS).toString();
  1366. }
  1367. else {
  1368. classes[counter] = null;
  1369. }
  1370. if (attr.isDefined(HTML.Attribute.ID)) {
  1371. ids[counter] = attr.getAttribute(HTML.Attribute.ID).
  1372. toString();
  1373. }
  1374. else {
  1375. ids[counter] = null;
  1376. }
  1377. }
  1378. else {
  1379. tags[counter] = ids[counter] = classes[counter] = null;
  1380. }
  1381. }
  1382. tags[0] = t.toString();
  1383. return createResolvedStyle(selector, tags, ids, classes);
  1384. }
  1385. /**
  1386. * Creates and returns a Style containing all the rules that match
  1387. * <code>selector</code>. It is assumed that each simple selector
  1388. * in <code>selector</code> is separated by a space.
  1389. */
  1390. private Style createResolvedStyle(String selector) {
  1391. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  1392. // Will contain the tags, ids, and classes, in that order.
  1393. Vector elements = sb.getVector();
  1394. try {
  1395. boolean done;
  1396. int dotIndex = 0;
  1397. int spaceIndex = 0;
  1398. int poundIndex = 0;
  1399. int lastIndex = 0;
  1400. int length = selector.length();
  1401. while (lastIndex < length) {
  1402. if (dotIndex == lastIndex) {
  1403. dotIndex = selector.indexOf('.', lastIndex);
  1404. }
  1405. if (poundIndex == lastIndex) {
  1406. poundIndex = selector.indexOf('#', lastIndex);
  1407. }
  1408. spaceIndex = selector.indexOf(' ', lastIndex);
  1409. if (spaceIndex == -1) {
  1410. spaceIndex = length;
  1411. }
  1412. if (dotIndex != -1 && poundIndex != -1 &&
  1413. dotIndex < spaceIndex && poundIndex < spaceIndex) {
  1414. if (poundIndex < dotIndex) {
  1415. // #.
  1416. if (lastIndex == poundIndex) {
  1417. elements.addElement("");
  1418. }
  1419. else {
  1420. elements.addElement(selector.substring(lastIndex,
  1421. poundIndex));
  1422. }
  1423. if ((dotIndex + 1) < spaceIndex) {
  1424. elements.addElement(selector.substring
  1425. (dotIndex + 1, spaceIndex));
  1426. }
  1427. else {
  1428. elements.addElement(null);
  1429. }
  1430. if ((poundIndex + 1) == dotIndex) {
  1431. elements.addElement(null);
  1432. }
  1433. else {
  1434. elements.addElement(selector.substring
  1435. (poundIndex + 1, dotIndex));
  1436. }
  1437. }
  1438. else if(poundIndex < spaceIndex) {
  1439. // .#
  1440. if (lastIndex == dotIndex) {
  1441. elements.addElement("");
  1442. }
  1443. else {
  1444. elements.addElement(selector.substring(lastIndex,
  1445. dotIndex));
  1446. }
  1447. if ((dotIndex + 1) < poundIndex) {
  1448. elements.addElement(selector.substring
  1449. (dotIndex + 1, poundIndex));
  1450. }
  1451. else {
  1452. elements.addElement(null);
  1453. }
  1454. if ((poundIndex + 1) == spaceIndex) {
  1455. elements.addElement(null);
  1456. }
  1457. else {
  1458. elements.addElement(selector.substring
  1459. (poundIndex + 1, spaceIndex));
  1460. }
  1461. }
  1462. dotIndex = poundIndex = spaceIndex + 1;
  1463. }
  1464. else if (dotIndex != -1 && dotIndex < spaceIndex) {
  1465. // .
  1466. if (dotIndex == lastIndex) {
  1467. elements.addElement("");
  1468. }
  1469. else {
  1470. elements.addElement(selector.substring(lastIndex,
  1471. dotIndex));
  1472. }
  1473. if ((dotIndex + 1) == spaceIndex) {
  1474. elements.addElement(null);
  1475. }
  1476. else {
  1477. elements.addElement(selector.substring(dotIndex + 1,
  1478. spaceIndex));
  1479. }
  1480. elements.addElement(null);
  1481. dotIndex = spaceIndex + 1;
  1482. }
  1483. else if (poundIndex != -1 && poundIndex < spaceIndex) {
  1484. // #
  1485. if (poundIndex == lastIndex) {
  1486. elements.addElement("");
  1487. }
  1488. else {
  1489. elements.addElement(selector.substring(lastIndex,
  1490. poundIndex));
  1491. }
  1492. elements.addElement(null