1. /*
  2. * @(#)StyleSheet.java 1.54 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing.text.html;
  8. import java.util.*;
  9. import java.awt.*;
  10. import java.io.*;
  11. import java.net.*;
  12. import javax.swing.Icon;
  13. import javax.swing.ImageIcon;
  14. import javax.swing.border.*;
  15. import javax.swing.event.ChangeListener;
  16. import javax.swing.text.*;
  17. /**
  18. * Support for defining the visual characteristics of
  19. * html views being rendered. The StyleSheet is used to
  20. * translate the html model into visual characteristics.
  21. * This enables views to be customized by a look-and-feel,
  22. * multiple views over the same model can be rendered
  23. * differently, etc. This can be thought of as a CSS
  24. * rule repository. The key for CSS attributes is an
  25. * object of type CSS.Attribute. The type of the value
  26. * is up to the StyleSheet implementation, but the
  27. * <code>toString</code> method is required
  28. * to return a string representation of CSS value.
  29. * <p>
  30. * The primary entry point for HTML View implementations
  31. * to get their attributes is the
  32. * <a href="#getViewAttributes">getViewAttributes</a>
  33. * method. This should be implemented to establish the
  34. * desired policy used to associate attributes with the view.
  35. * Each HTMLEditorKit (i.e. and therefore each associated
  36. * JEditorPane) can have its own StyleSheet, but by default one
  37. * sheet will be shared by all of the HTMLEditorKit instances.
  38. * HTMLDocument instance can also have a StyleSheet, which
  39. * holds the document-specific CSS specifications.
  40. * <p>
  41. * In order for Views to store less state and therefore be
  42. * more lightweight, the StyleSheet can act as a factory for
  43. * painters that handle some of the rendering tasks. This allows
  44. * implementations to determine what they want to cache
  45. * and have the sharing potentially at the level that a
  46. * selector is common to multiple views. Since the StyleSheet
  47. * may be used by views over multiple documents and typically
  48. * the html attributes don't effect the selector being used,
  49. * the potential for sharing is significant.
  50. * <p>
  51. * The rules are stored as named styles, and other information
  52. * is stored to translate the context of an element to a
  53. * rule quickly. The following code fragment will display
  54. * the named styles, and therefore the CSS rules contained.
  55. * <code><pre>
  56. *  
  57. *   import java.util.*;
  58. *   import javax.swing.text.*;
  59. *   import javax.swing.text.html.*;
  60. *  
  61. *   public class ShowStyles {
  62. *  
  63. *   public static void main(String[] args) {
  64. *   HTMLEditorKit kit = new HTMLEditorKit();
  65. *   HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
  66. *   StyleSheet styles = doc.getStyleSheet();
  67. *  
  68. *   Enumeration rules = styles.getStyleNames();
  69. *   while (rules.hasMoreElements()) {
  70. *   String name = (String) rules.nextElement();
  71. *   Style rule = styles.getStyle(name);
  72. *   System.out.println(rule.toString());
  73. *   }
  74. *   System.exit(0);
  75. *   }
  76. *   }
  77. *  
  78. * </pre></code>
  79. * <p>
  80. * <font color="red">Note: This implementation is currently
  81. * incomplete. It can be replaced with alternative implementations
  82. * that are complete. Future versions of this class will provide
  83. * better CSS support.</font>
  84. *
  85. * @author Timothy Prinzing
  86. * @author Sunita Mani
  87. * @author Sara Swanson
  88. * @author Jill Nakata
  89. * @version 1.54 11/29/01
  90. */
  91. public class StyleSheet extends StyleContext {
  92. /**
  93. * Construct a StyleSheet
  94. */
  95. public StyleSheet() {
  96. super();
  97. selectorMapping = new Hashtable();
  98. resolvedStyles = new Hashtable();
  99. if (css == null) {
  100. css = new CSS();
  101. }
  102. }
  103. /**
  104. * Fetch the style to use to render the given type
  105. * of html tag. The element given is representing
  106. * the tag and can be used to determine the nesting
  107. * for situations where the attributes will differ
  108. * if nesting inside of elements.
  109. *
  110. * @param t the type to translate to visual attributes.
  111. * @param e the element representing the tag. The element
  112. * can be used to determine the nesting for situations where
  113. * the attributes will differ if nested inside of other
  114. * elements.
  115. * @returns the set of css attributes to use to render
  116. * the tag.
  117. */
  118. public Style getRule(HTML.Tag t, Element e) {
  119. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  120. try {
  121. // Build an array of all the parent elements.
  122. Vector searchContext = sb.getVector();
  123. for (Element p = e; p != null; p = p.getParentElement()) {
  124. searchContext.addElement(p);
  125. }
  126. // Build a fully qualified selector.
  127. int n = searchContext.size();
  128. StringBuffer cacheLookup = sb.getStringBuffer();
  129. AttributeSet attr;
  130. String eName;
  131. Object name;
  132. // >= 1 as the HTML.Tag for the 0th element is passed in.
  133. for (int counter = n - 1; counter >= 1; counter--) {
  134. e = (Element)searchContext.elementAt(counter);
  135. attr = e.getAttributes();
  136. name = attr.getAttribute(StyleConstants.NameAttribute);
  137. if (name instanceof HTML.Tag) {
  138. if (name == HTML.Tag.IMPLIED) {
  139. eName = HTML.Tag.P.toString();
  140. }
  141. else {
  142. eName = name.toString();
  143. }
  144. }
  145. else {
  146. eName = name.toString();
  147. }
  148. cacheLookup.append(eName);
  149. if (attr != null) {
  150. if (attr.isDefined(HTML.Attribute.ID)) {
  151. cacheLookup.append('#');
  152. cacheLookup.append(attr.getAttribute
  153. (HTML.Attribute.ID));
  154. }
  155. else if (attr.isDefined(HTML.Attribute.CLASS)) {
  156. cacheLookup.append('.');
  157. cacheLookup.append(attr.getAttribute
  158. (HTML.Attribute.CLASS));
  159. }
  160. }
  161. cacheLookup.append(' ');
  162. }
  163. if (t == HTML.Tag.IMPLIED) {
  164. t = HTML.Tag.P;
  165. }
  166. cacheLookup.append(t.toString());
  167. e = (Element)searchContext.elementAt(0);
  168. attr = e.getAttributes();
  169. if (attr != null) {
  170. if (attr.isDefined(HTML.Attribute.ID)) {
  171. cacheLookup.append('#');
  172. cacheLookup.append(attr.getAttribute(HTML.Attribute.ID));
  173. }
  174. else if (attr.isDefined(HTML.Attribute.CLASS)) {
  175. cacheLookup.append('.');
  176. cacheLookup.append(attr.getAttribute
  177. (HTML.Attribute.CLASS));
  178. }
  179. }
  180. Style style = getResolvedStyle(cacheLookup.toString(),
  181. searchContext, t);
  182. return style;
  183. }
  184. finally {
  185. SearchBuffer.releaseSearchBuffer(sb);
  186. }
  187. }
  188. /**
  189. * Fetch the rule that best matches the selector given
  190. * in string form. Where <code>selector</code> is a space separated
  191. * String of the element names. For example, <code>selector</code>
  192. * might be 'html body tr td''<p>
  193. * The attributes of the returned Style will change
  194. * as rules are added and removed. That is if you to ask for a rule
  195. * with a selector "table p" and a new rule was added with a selector
  196. * of "p" the returned Style would include the new attributes from
  197. * the rule "p".
  198. */
  199. public Style getRule(String selector) {
  200. selector = cleanSelectorString(selector);
  201. if (selector != null) {
  202. Style style = getResolvedStyle(selector);
  203. return style;
  204. }
  205. return null;
  206. }
  207. /**
  208. * Add a set of rules to the sheet. The rules are expected to
  209. * be in valid CSS format. Typically this would be called as
  210. * a result of parsing a <style> tag.
  211. */
  212. public void addRule(String rule) {
  213. if (rule != null) {
  214. CssParser parser = new CssParser();
  215. try {
  216. parser.parse(getBase(), new StringReader(rule), false, false);
  217. } catch (IOException ioe) { }
  218. }
  219. }
  220. /**
  221. * Translate a CSS declaration to an AttributeSet that represents
  222. * the CSS declaration. Typically this would be called as a
  223. * result of encountering an HTML style attribute.
  224. */
  225. public AttributeSet getDeclaration(String decl) {
  226. if (decl == null) {
  227. return SimpleAttributeSet.EMPTY;
  228. }
  229. CssParser parser = new CssParser();
  230. return parser.parseDeclaration(decl);
  231. }
  232. /**
  233. * Load a set of rules that have been specified in terms of
  234. * CSS1 grammar. If there are collisions with existing rules,
  235. * the newly specified rule will win.
  236. *
  237. * @param in the stream to read the css grammar from.
  238. * @param ref the reference url. This value represents the
  239. * location of the stream and may be null. All relative
  240. * urls specified in the stream will be based upon this
  241. * parameter.
  242. */
  243. public void loadRules(Reader in, URL ref) throws IOException {
  244. CssParser parser = new CssParser();
  245. parser.parse(ref, in, false, false);
  246. }
  247. /**
  248. * Fetch a set of attributes to use in the view for
  249. * displaying. This is basically a set of attributes that
  250. * can be used for View.getAttributes.
  251. */
  252. public AttributeSet getViewAttributes(View v) {
  253. return new ViewAttributeSet(v);
  254. }
  255. /**
  256. * Removes a named style previously added to the document.
  257. *
  258. * @param nm the name of the style to remove
  259. */
  260. public void removeStyle(String nm) {
  261. Style aStyle = getStyle(nm);
  262. if (aStyle != null) {
  263. String selector = cleanSelectorString(nm);
  264. String[] selectors = getSimpleSelectors(selector);
  265. synchronized(this) {
  266. Object mapping = getRootSelectorMapping();
  267. for (int i = selectors.length - 1; i >= 0; i--) {
  268. mapping = getSelectorMapping(mapping, selectors[i], true);
  269. }
  270. Style rule = getMappingStyle(mapping);
  271. if (rule != null) {
  272. removeMappingStyle(mapping);
  273. if (resolvedStyles.size() > 0) {
  274. Enumeration values = resolvedStyles.elements();
  275. while (values.hasMoreElements()) {
  276. ResolvedStyle style = (ResolvedStyle)values.
  277. nextElement();
  278. style.removeStyle(rule);
  279. }
  280. }
  281. }
  282. }
  283. }
  284. super.removeStyle(nm);
  285. }
  286. /**
  287. * Adds the rules from the StyleSheet <code>ss</code> to those of
  288. * the receiver. <code>ss's</code> rules will override the rules of
  289. * any previously added style sheets. An added StyleSheet will never
  290. * override the rules of the receiving style sheet.
  291. */
  292. /* public */
  293. synchronized void addStyleSheet(StyleSheet ss) {
  294. if (linkedStyleSheets == null) {
  295. linkedStyleSheets = new Vector();
  296. }
  297. if (!linkedStyleSheets.contains(ss)) {
  298. linkedStyleSheets.insertElementAt(ss, 0);
  299. linkStyleSheetAt(ss, 0);
  300. }
  301. }
  302. /**
  303. * Removes the StyleSheet <code>ss</code> from those of the receiver.
  304. */
  305. /* public */
  306. synchronized void removeStyleSheet(StyleSheet ss) {
  307. if (linkedStyleSheets != null) {
  308. int index = linkedStyleSheets.indexOf(ss);
  309. if (index != -1) {
  310. linkedStyleSheets.removeElementAt(index);
  311. unlinkStyleSheet(ss, index);
  312. if (index == 0 && linkedStyleSheets.size() == 0) {
  313. linkedStyleSheets = null;
  314. }
  315. }
  316. }
  317. }
  318. //
  319. // The following is used to import style sheets.
  320. //
  321. /**
  322. * Returns an enumeration of the linked StyleSheets. Will return null
  323. * if there are no linked StyleSheets.
  324. */
  325. /* public */
  326. synchronized Enumeration getStyleSheets() {
  327. if (linkedStyleSheets == null) {
  328. return null;
  329. }
  330. return linkedStyleSheets.elements();
  331. }
  332. /**
  333. * Imports a style sheet from <code>url</code>. The resulting rules
  334. * are directly added to the receiver. If you do not want the rules
  335. * to become part of the receiver, create a new StyleSheet and use
  336. * addStyleSheet to link it in.
  337. */
  338. void importStyleSheet(URL url) {
  339. try {
  340. InputStream is;
  341. is = url.openStream();
  342. Reader r = new BufferedReader(new InputStreamReader(is));
  343. CssParser parser = new CssParser();
  344. parser.parse(url, r, false, true);
  345. r.close();
  346. is.close();
  347. } catch (Throwable e) {
  348. // on error we simply have no styles... the html
  349. // will look mighty wrong but still function.
  350. }
  351. }
  352. /**
  353. * Sets the base. All import statements that are relative, will be
  354. * relative to <code>base</code>.
  355. */
  356. /* public */
  357. void setBase(URL base) {
  358. this.base = base;
  359. }
  360. /**
  361. * Returns the base.
  362. */
  363. /* public */
  364. URL getBase() {
  365. return base;
  366. }
  367. /**
  368. * Add a CSS attribute to the given set.
  369. */
  370. boolean addCSSAttribute(MutableAttributeSet attr, CSS.Attribute key,
  371. String value) {
  372. Object iValue = css.getInternalCSSValue(key, value);
  373. if (iValue != null) {
  374. attr.addAttribute(key, iValue);
  375. return true;
  376. }
  377. return false;
  378. }
  379. /**
  380. * Add a CSS attribute to the given set.
  381. */
  382. boolean addCSSAttributeFromHTML(MutableAttributeSet attr,
  383. CSS.Attribute key, String value) {
  384. Object iValue = css.getCssValue(key, value);
  385. if (iValue != null) {
  386. attr.addAttribute(key, iValue);
  387. return true;
  388. }
  389. return false;
  390. }
  391. // ---- Conversion functionality ---------------------------------
  392. /**
  393. * Convert a set of html attributes to an equivalent
  394. * set of css attributes.
  395. *
  396. * @param AttributeSet containing the HTML attributes.
  397. * @param AttributeSet containing the corresponding CSS attributes.
  398. * The AttributeSet will be empty if there are no mapping
  399. * CSS attributes.
  400. */
  401. public AttributeSet translateHTMLToCSS(AttributeSet htmlAttrSet) {
  402. AttributeSet cssAttrSet = css.translateHTMLToCSS(htmlAttrSet);
  403. MutableAttributeSet cssStyleSet = addStyle(null, null);
  404. cssStyleSet.addAttributes(cssAttrSet);
  405. return cssStyleSet;
  406. }
  407. /**
  408. * Adds an attribute to the given set, and returns
  409. * the new representative set. This is reimplemented to
  410. * convert StyleConstant attributes to CSS prior to forwarding
  411. * to the superclass behavior. The the StyleConstants attribute
  412. * has no corresponding CSS entry, the StyleConstants attribute
  413. * is stored (but will likely be unused).
  414. *
  415. * @param old the old attribute set
  416. * @param key the non-null attribute key
  417. * @param value the attribute value
  418. * @return the updated attribute set
  419. * @see MutableAttributeSet#addAttribute
  420. */
  421. public AttributeSet addAttribute(AttributeSet old, Object key,
  422. Object value) {
  423. if (css == null) {
  424. // supers constructor will call this before returning,
  425. // and we need to make sure CSS is non null.
  426. css = new CSS();
  427. }
  428. if (key instanceof StyleConstants) {
  429. Object cssValue = css.styleConstantsValueToCSSValue
  430. ((StyleConstants)key, value);
  431. if (cssValue != null) {
  432. Object cssKey = css.styleConstantsKeyToCSSKey
  433. ((StyleConstants)key);
  434. if (cssKey != null) {
  435. return super.addAttribute(old, cssKey, cssValue);
  436. }
  437. }
  438. }
  439. return super.addAttribute(old, key, value);
  440. }
  441. /**
  442. * Adds a set of attributes to the element. If any of these attributes
  443. * are StyleConstants attributes, they will be converted to CSS prior
  444. * to forwarding to the superclass behavior.
  445. *
  446. * @param old the old attribute set
  447. * @param attr the attributes to add
  448. * @return the updated attribute set
  449. * @see MutableAttributeSet#addAttribute
  450. */
  451. public AttributeSet addAttributes(AttributeSet old, AttributeSet attr) {
  452. return super.addAttributes(old, convertAttributeSet(attr));
  453. }
  454. /**
  455. * Removes an attribute from the set. If the attribute is a StyleConstants
  456. * attribute, the request will be converted to a CSS attribute prior to
  457. * forwarding to the superclass behavior.
  458. *
  459. * @param old the old set of attributes
  460. * @param key the non-null attribute name
  461. * @return the updated attribute set
  462. * @see MutableAttributeSet#removeAttribute
  463. */
  464. public AttributeSet removeAttribute(AttributeSet old, Object key) {
  465. if (key instanceof StyleConstants) {
  466. Object cssKey = css.styleConstantsKeyToCSSKey((StyleConstants)key);
  467. if (cssKey != null) {
  468. return super.removeAttribute(old, cssKey);
  469. }
  470. }
  471. return super.removeAttribute(old, key);
  472. }
  473. /**
  474. * Removes a set of attributes for the element. If any of the attributes
  475. * is a StyleConstants attribute, the request will be converted to a CSS
  476. * attribute prior to forwarding to the superclass behavior.
  477. *
  478. * @param old the old attribute set
  479. * @param names the attribute names
  480. * @return the updated attribute set
  481. * @see MutableAttributeSet#removeAttributes
  482. */
  483. public AttributeSet removeAttributes(AttributeSet old, Enumeration names) {
  484. return super.removeAttributes(old, names);
  485. }
  486. /**
  487. * Removes a set of attributes. If any of the attributes
  488. * is a StyleConstants attribute, the request will be converted to a CSS
  489. * attribute prior to forwarding to the superclass behavior.
  490. *
  491. * @param old the old attribute set
  492. * @param attrs the attributes
  493. * @return the updated attribute set
  494. * @see MutableAttributeSet#removeAttributes
  495. */
  496. public AttributeSet removeAttributes(AttributeSet old, AttributeSet attrs) {
  497. return super.removeAttributes(old, convertAttributeSet(attrs));
  498. }
  499. /**
  500. * Create a compact set of attributes that might be shared.
  501. * This is a hook for subclasses that want to alter the
  502. * behavior of SmallAttributeSet. This can be reimplemented
  503. * to return an AttributeSet that provides some sort of
  504. * attribute conversion.
  505. *
  506. * @param a The set of attributes to be represented in the
  507. * the compact form.
  508. */
  509. protected SmallAttributeSet createSmallAttributeSet(AttributeSet a) {
  510. return new SmallConversionSet(a);
  511. }
  512. /**
  513. * Create a large set of attributes that should trade off
  514. * space for time. This set will not be shared. This is
  515. * a hook for subclasses that want to alter the behavior
  516. * of the larger attribute storage format (which is
  517. * SimpleAttributeSet by default). This can be reimplemented
  518. * to return a MutableAttributeSet that provides some sort of
  519. * attribute conversion.
  520. *
  521. * @param a The set of attributes to be represented in the
  522. * the larger form.
  523. */
  524. protected MutableAttributeSet createLargeAttributeSet(AttributeSet a) {
  525. return new LargeConversionSet(a);
  526. }
  527. /**
  528. * Convert a set of attributes (if necessary) so that
  529. * any attributes that were specified as StyleConstants
  530. * attributes and have a CSS mapping, will be converted
  531. * to CSS attributes.
  532. */
  533. AttributeSet convertAttributeSet(AttributeSet a) {
  534. if ((a instanceof LargeConversionSet) ||
  535. (a instanceof SmallConversionSet)) {
  536. // known to be converted.
  537. return a;
  538. }
  539. // in most cases, there are no StyleConstants attributes
  540. // so we iterate the collection of keys to avoid creating
  541. // a new set.
  542. Enumeration names = a.getAttributeNames();
  543. while (names.hasMoreElements()) {
  544. Object name = names.nextElement();
  545. if (name instanceof StyleConstants) {
  546. // we really need to do a conversion, iterate again
  547. // building a new set.
  548. MutableAttributeSet converted = new LargeConversionSet();
  549. Enumeration keys = a.getAttributeNames();
  550. while (keys.hasMoreElements()) {
  551. Object key = keys.nextElement();
  552. Object cssValue = null;
  553. if (key instanceof StyleConstants) {
  554. // convert the StyleConstants attribute if possible
  555. Object cssKey = css.styleConstantsKeyToCSSKey
  556. ((StyleConstants)key);
  557. if (cssKey != null) {
  558. Object value = a.getAttribute(key);
  559. cssValue = css.styleConstantsValueToCSSValue
  560. ((StyleConstants)key, value);
  561. if (cssValue != null) {
  562. converted.addAttribute(cssKey, cssValue);
  563. }
  564. }
  565. }
  566. if (cssValue == null) {
  567. converted.addAttribute(key, a.getAttribute(key));
  568. }
  569. }
  570. return converted;
  571. }
  572. }
  573. return a;
  574. }
  575. /**
  576. * Large set of attributes that does conversion of requests
  577. * for attributes of type StyleConstants.
  578. */
  579. class LargeConversionSet extends SimpleAttributeSet {
  580. /**
  581. * Creates a new attribute set based on a supplied set of attributes.
  582. *
  583. * @param source the set of attributes
  584. */
  585. public LargeConversionSet(AttributeSet source) {
  586. super(source);
  587. }
  588. public LargeConversionSet() {
  589. super();
  590. }
  591. /**
  592. * Checks whether a given attribute is defined.
  593. *
  594. * @param key the attribute key
  595. * @return true if the attribute is defined
  596. * @see AttributeSet#isDefined
  597. */
  598. public boolean isDefined(Object key) {
  599. if (key instanceof StyleConstants) {
  600. Object cssKey = css.styleConstantsKeyToCSSKey
  601. ((StyleConstants)key);
  602. if (cssKey != null) {
  603. return super.isDefined(cssKey);
  604. }
  605. }
  606. return super.isDefined(key);
  607. }
  608. /**
  609. * Gets the value of an attribute.
  610. *
  611. * @param key the attribute name
  612. * @return the attribute value
  613. * @see AttributeSet#getAttribute
  614. */
  615. public Object getAttribute(Object key) {
  616. if (key instanceof StyleConstants) {
  617. Object cssKey = css.styleConstantsKeyToCSSKey
  618. ((StyleConstants)key);
  619. if (cssKey != null) {
  620. Object value = super.getAttribute(cssKey);
  621. if (value != null) {
  622. return css.cssValueToStyleConstantsValue
  623. ((StyleConstants)key, value);
  624. }
  625. }
  626. }
  627. return super.getAttribute(key);
  628. }
  629. }
  630. /**
  631. * small set of attributes that does conversion of requests
  632. * for attributes of type StyleConstants.
  633. */
  634. class SmallConversionSet extends SmallAttributeSet {
  635. /**
  636. * Creates a new attribute set based on a supplied set of attributes.
  637. *
  638. * @param source the set of attributes
  639. */
  640. public SmallConversionSet(AttributeSet attrs) {
  641. super(attrs);
  642. }
  643. /**
  644. * Checks whether a given attribute is defined.
  645. *
  646. * @param key the attribute key
  647. * @return true if the attribute is defined
  648. * @see AttributeSet#isDefined
  649. */
  650. public boolean isDefined(Object key) {
  651. if (key instanceof StyleConstants) {
  652. Object cssKey = css.styleConstantsKeyToCSSKey
  653. ((StyleConstants)key);
  654. if (cssKey != null) {
  655. return super.isDefined(cssKey);
  656. }
  657. }
  658. return super.isDefined(key);
  659. }
  660. /**
  661. * Gets the value of an attribute.
  662. *
  663. * @param key the attribute name
  664. * @return the attribute value
  665. * @see AttributeSet#getAttribute
  666. */
  667. public Object getAttribute(Object key) {
  668. if (key instanceof StyleConstants) {
  669. Object cssKey = css.styleConstantsKeyToCSSKey
  670. ((StyleConstants)key);
  671. if (cssKey != null) {
  672. Object value = super.getAttribute(cssKey);
  673. if (value != null) {
  674. return css.cssValueToStyleConstantsValue
  675. ((StyleConstants)key, value);
  676. }
  677. }
  678. }
  679. return super.getAttribute(key);
  680. }
  681. }
  682. // ---- Resource handling ----------------------------------------
  683. /**
  684. * Fetch the font to use for the given set of attributes.
  685. */
  686. public Font getFont(AttributeSet a) {
  687. return css.getFont(this, a, 12);
  688. }
  689. /**
  690. * Takes a set of attributes and turn it into a foreground color
  691. * specification. This might be used to specify things
  692. * like brighter, more hue, etc.
  693. *
  694. * @param a the set of attributes
  695. * @return the color
  696. */
  697. public Color getForeground(AttributeSet a) {
  698. Color c = css.getColor(a, CSS.Attribute.COLOR);
  699. if (c == null) {
  700. return Color.black;
  701. }
  702. return c;
  703. }
  704. /**
  705. * Takes a set of attributes and turn it into a background color
  706. * specification. This might be used to specify things
  707. * like brighter, more hue, etc.
  708. *
  709. * @param attr the set of attributes
  710. * @return the color
  711. */
  712. public Color getBackground(AttributeSet a) {
  713. return css.getColor(a, CSS.Attribute.BACKGROUND_COLOR);
  714. }
  715. /**
  716. * Fetch the box formatter to use for the given set
  717. * of css attributes.
  718. */
  719. public BoxPainter getBoxPainter(AttributeSet a) {
  720. return new BoxPainter(a, css, this);
  721. }
  722. /**
  723. * Fetch the list formatter to use for the given set
  724. * of css attributes.
  725. */
  726. public ListPainter getListPainter(AttributeSet a) {
  727. return new ListPainter(a);
  728. }
  729. public void setBaseFontSize(int sz) {
  730. css.setBaseFontSize(sz);
  731. }
  732. public void setBaseFontSize(String size) {
  733. css.setBaseFontSize(size);
  734. }
  735. public static int getIndexOfSize(float pt) {
  736. return CSS.getIndexOfSize(pt);
  737. }
  738. /**
  739. * Return the point size, given a size index.
  740. */
  741. public float getPointSize(int index) {
  742. return css.getPointSize(index);
  743. }
  744. /**
  745. * Given a string "+2", "-2", "2".
  746. * returns a point size value
  747. */
  748. public float getPointSize(String size) {
  749. return css.getPointSize(size);
  750. }
  751. /**
  752. * Convert a color string "RED" or "#NNNNNN" to a Color.
  753. * Note: This will only convert the HTML3.2 colors strings
  754. * or string of length 7
  755. * otherwise, it will return null.
  756. */
  757. public Color stringToColor(String string) {
  758. return CSS.stringToColor(string);
  759. }
  760. /**
  761. * This method adds a rule into the StyleSheet.
  762. *
  763. * @param selector the selector to use for the rule.
  764. * This will be a set of simple selectors, and must
  765. * be a length of 1 or greater.
  766. * @param declaration the set of css attributes that
  767. * make up the rule.
  768. */
  769. void addRule(String[] selector, AttributeSet declaration,
  770. boolean isLinked) {
  771. int n = selector.length;
  772. StringBuffer sb = new StringBuffer();
  773. sb.append(selector[0]);
  774. for (int counter = 1; counter < n; counter++) {
  775. sb.append(' ');
  776. sb.append(selector[counter]);
  777. }
  778. String selectorName = sb.toString();
  779. Style rule = getStyle(selectorName);
  780. if (rule == null) {
  781. // Notice how the rule is first created, and it not part of
  782. // the synchronized block. It is done like this as creating
  783. // a new rule will fire a ChangeEvent. We do not want to be
  784. // holding the lock when calling to other objects, it can
  785. // result in deadlock.
  786. Style altRule = addStyle(selectorName, null);
  787. synchronized(this) {
  788. Object mapping = getRootSelectorMapping();
  789. for (int i = n - 1; i >= 0; i--) {
  790. mapping = getSelectorMapping(mapping, selector[i], true);
  791. }
  792. rule = getMappingStyle(mapping);
  793. if (rule == null) {
  794. rule = createStyleForSelector(selectorName, mapping,
  795. altRule);
  796. refreshResolvedRules(selectorName, selector, rule,
  797. getSpecificity(mapping));
  798. }
  799. }
  800. }
  801. if (isLinked) {
  802. rule = getLinkedStyle(rule);
  803. }
  804. rule.addAttributes(declaration);
  805. }
  806. //
  807. // The following gaggle of methods is used in maintaing the rules from
  808. // the sheet.
  809. //
  810. /**
  811. * Updates the attributes of the rules to reference any related
  812. * rules in <code>ss</code>.
  813. */
  814. private synchronized void linkStyleSheetAt(StyleSheet ss, int index) {
  815. if (resolvedStyles.size() > 0) {
  816. Enumeration values = resolvedStyles.elements();
  817. while (values.hasMoreElements()) {
  818. ResolvedStyle rule = (ResolvedStyle)values.nextElement();
  819. rule.insertExtendedStyleAt(ss.getRule(rule.getName()),
  820. index);
  821. }
  822. }
  823. }
  824. /**
  825. * Removes references to the rules in <code>ss</code>.
  826. * <code>index</code> gives the index the StyleSheet was at, that is
  827. * how many StyleSheets had been added before it.
  828. */
  829. private synchronized void unlinkStyleSheet(StyleSheet ss, int index) {
  830. if (resolvedStyles.size() > 0) {
  831. Enumeration values = resolvedStyles.elements();
  832. while (values.hasMoreElements()) {
  833. ResolvedStyle rule = (ResolvedStyle)values.nextElement();
  834. rule.removeExtendedStyleAt(index);
  835. }
  836. }
  837. }
  838. /**
  839. * @return simple selectors that comprise selector.
  840. */
  841. /* protected */
  842. String[] getSimpleSelectors(String selector) {
  843. selector = cleanSelectorString(selector);
  844. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  845. Vector selectors = sb.getVector();
  846. int lastIndex = 0;
  847. int length = selector.length();
  848. while (lastIndex != -1) {
  849. int newIndex = selector.indexOf(' ', lastIndex);
  850. if (newIndex != -1) {
  851. selectors.addElement(selector.substring(lastIndex, newIndex));
  852. if (++newIndex == length) {
  853. lastIndex = -1;
  854. }
  855. else {
  856. lastIndex = newIndex;
  857. }
  858. }
  859. else {
  860. selectors.addElement(selector.substring(lastIndex));
  861. lastIndex = -1;
  862. }
  863. }
  864. String[] retValue = new String[selectors.size()];
  865. selectors.copyInto(retValue);
  866. SearchBuffer.releaseSearchBuffer(sb);
  867. return retValue;
  868. }
  869. /**
  870. * Returns a string that only has one space between simple selectors,
  871. * which may be the passed in String.
  872. */
  873. /*protected*/ String cleanSelectorString(String selector) {
  874. boolean lastWasSpace = true;
  875. for (int counter = 0, maxCounter = selector.length();
  876. counter < maxCounter; counter++) {
  877. switch(selector.charAt(counter)) {
  878. case ' ':
  879. if (lastWasSpace) {
  880. return _cleanSelectorString(selector);
  881. }
  882. lastWasSpace = true;
  883. break;
  884. case '\n':
  885. case '\r':
  886. case '\t':
  887. return _cleanSelectorString(selector);
  888. default:
  889. lastWasSpace = false;
  890. }
  891. }
  892. if (lastWasSpace) {
  893. return _cleanSelectorString(selector);
  894. }
  895. // It was fine.
  896. return selector;
  897. }
  898. /**
  899. * Returns a new String that contains only one space between non
  900. * white space characters.
  901. */
  902. private String _cleanSelectorString(String selector) {
  903. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  904. StringBuffer buff = sb.getStringBuffer();
  905. boolean lastWasSpace = true;
  906. int lastIndex = 0;
  907. char[] chars = selector.toCharArray();
  908. int numChars = chars.length;
  909. String retValue = null;
  910. try {
  911. for (int counter = 0; counter < numChars; counter++) {
  912. switch(chars[counter]) {
  913. case ' ':
  914. if (!lastWasSpace) {
  915. lastWasSpace = true;
  916. if (lastIndex < counter) {
  917. buff.append(chars, lastIndex,
  918. 1 + counter - lastIndex);
  919. }
  920. }
  921. lastIndex = counter + 1;
  922. break;
  923. case '\n':
  924. case '\r':
  925. case '\t':
  926. if (!lastWasSpace) {
  927. lastWasSpace = true;
  928. if (lastIndex < counter) {
  929. buff.append(chars, lastIndex,
  930. counter - lastIndex);
  931. buff.append(' ');
  932. }
  933. }
  934. lastIndex = counter + 1;
  935. break;
  936. default:
  937. lastWasSpace = false;
  938. break;
  939. }
  940. }
  941. if (lastWasSpace && buff.length() > 0) {
  942. // Remove last space.
  943. buff.setLength(buff.length() - 1);
  944. }
  945. else if (lastIndex < numChars) {
  946. buff.append(chars, lastIndex, numChars - lastIndex);
  947. }
  948. retValue = buff.toString();
  949. }
  950. finally {
  951. SearchBuffer.releaseSearchBuffer(sb);
  952. }
  953. return retValue;
  954. }
  955. /**
  956. * Returns the root selector mapping that all selectors are relative
  957. * too. This is an inverted graph of the selectors.
  958. */
  959. private Object getRootSelectorMapping() {
  960. return selectorMapping;
  961. }
  962. /**
  963. * Returns the child mapping of <code>parent</code> for
  964. * <code>selector</code>. If there is no mapping for <code>selector</code>
  965. * and <code>create</code> is false, this will return null.
  966. */
  967. private synchronized Object getSelectorMapping(Object parent,
  968. String selector,
  969. boolean create) {
  970. Hashtable retValue = (Hashtable)((Hashtable)parent).get(selector);
  971. if (retValue == null && create) {
  972. retValue = new Hashtable(7);
  973. ((Hashtable)parent).put(selector, retValue);
  974. // Update specificity for child.
  975. int specificity = 0;
  976. if (parent != null) {
  977. Object pSpec = ((Hashtable)parent).get(SPECIFICITY);
  978. if (pSpec != null) {
  979. specificity = ((Integer)pSpec).intValue();
  980. }
  981. }
  982. // class (.) 100
  983. // id (#) 10000
  984. char firstChar = selector.charAt(0);
  985. if (firstChar == '.') {
  986. specificity += 100;
  987. }
  988. else if (firstChar == '#') {
  989. specificity += 10000;
  990. }
  991. else {
  992. specificity += 1;
  993. if (selector.indexOf('.') != -1) {
  994. specificity += 100;
  995. }
  996. if (selector.indexOf('#') != -1) {
  997. specificity += 10000;
  998. }
  999. }
  1000. retValue.put(SPECIFICITY, new Integer(specificity));
  1001. }
  1002. return retValue;
  1003. }
  1004. /**
  1005. * Returns the specificity of the passed in String. It assumes the
  1006. * passed in string doesn't contain junk, that is each selector is
  1007. * separated by a space and each selector at most contains one . or one
  1008. * #. A simple selector has a weight of 1, an id selector has a weight
  1009. * of 100, and a class selector has a weight of 10000.
  1010. */
  1011. /*protected*/ static int getSpecificity(String selector) {
  1012. int specificity = 0;
  1013. boolean lastWasSpace = true;
  1014. for (int counter = 0, maxCounter = selector.length();
  1015. counter < maxCounter; counter++) {
  1016. switch(selector.charAt(counter)) {
  1017. case '.':
  1018. specificity += 100;
  1019. break;
  1020. case '#':
  1021. specificity += 10000;
  1022. break;
  1023. case ' ':
  1024. lastWasSpace = true;
  1025. break;
  1026. default:
  1027. if (lastWasSpace) {
  1028. lastWasSpace = false;
  1029. specificity += 1;
  1030. }
  1031. }
  1032. }
  1033. return specificity;
  1034. }
  1035. /**
  1036. * Returns the specificity of the passed in mapping.
  1037. */
  1038. private int getSpecificity(Object mapping) {
  1039. Object pSpec = ((Hashtable)mapping).get(SPECIFICITY);
  1040. if (pSpec != null) {
  1041. return ((Integer)pSpec).intValue();
  1042. }
  1043. return 0;
  1044. }
  1045. /**
  1046. * Returns the style for the passed in mapping.
  1047. */
  1048. private Style getMappingStyle(Object mapping) {
  1049. return (Style)((Hashtable)mapping).get(RULE);
  1050. }
  1051. /**
  1052. * Removes the previously added mapping style.
  1053. */
  1054. private void removeMappingStyle(Object mapping) {
  1055. ((Hashtable)mapping).remove(RULE);
  1056. }
  1057. /**
  1058. * Returns the style that linked attributes should be added to. This
  1059. * will create the style if necessary.
  1060. */
  1061. private Style getLinkedStyle(Style localStyle) {
  1062. // NOTE: This is not synchronized, and the caller of this does
  1063. // not synchronize. There is the chance for one of the callers to
  1064. // overwrite the existing resolved parent, but it is quite rare.
  1065. // The reason this is left like this is because setResolveParent
  1066. // will fire a ChangeEvent. It is really, REALLY bad for us to
  1067. // hold a lock when calling outside of us, it may cause a deadlock.
  1068. Style retStyle = (Style)localStyle.getResolveParent();
  1069. if (retStyle == null) {
  1070. retStyle = addStyle(null, null);
  1071. localStyle.setResolveParent(retStyle);
  1072. }
  1073. return retStyle;
  1074. }
  1075. /**
  1076. * Returns the Style appropriate for <code>selector</code> and
  1077. * <code>mapping</code>. If a Style does not currently exist,
  1078. * <code>altStyle</code> will be used.
  1079. */
  1080. private synchronized Style createStyleForSelector(String selector,
  1081. Object mapping,
  1082. Style altStyle) {
  1083. Style style = (Style)((Hashtable)mapping).get(RULE);
  1084. if (style == null) {
  1085. style = altStyle;
  1086. ((Hashtable)mapping).put(RULE, altStyle);
  1087. }
  1088. return style;
  1089. }
  1090. /**
  1091. * Returns the resolved style for <code>selector</code>. This will
  1092. * create the resolved style, if necessary.
  1093. */
  1094. private synchronized Style getResolvedStyle(String selector,
  1095. Vector elements,
  1096. HTML.Tag t) {
  1097. Style retStyle = (Style)resolvedStyles.get(selector);
  1098. if (retStyle == null) {
  1099. retStyle = createResolvedStyle(selector, elements, t);
  1100. }
  1101. return retStyle;
  1102. }
  1103. /**
  1104. * Returns the resolved style for <code>selector</code>. This will
  1105. * create the resolved style, if necessary.
  1106. */
  1107. private synchronized Style getResolvedStyle(String selector) {
  1108. Style retStyle = (Style)resolvedStyles.get(selector);
  1109. if (retStyle == null) {
  1110. retStyle = createResolvedStyle(selector);
  1111. }
  1112. return retStyle;
  1113. }
  1114. /**
  1115. * Adds <code>mapping</code> to <code>elements</code>. It is added
  1116. * such that <code>elements</code> will remain ordered by
  1117. * specificity.
  1118. */
  1119. private void addSortedStyle(Object mapping, Vector elements) {
  1120. int size = elements.size();
  1121. if (size > 0) {
  1122. int specificity = getSpecificity(mapping);
  1123. for (int counter = 0; counter < size; counter++) {
  1124. if (specificity >= getSpecificity(elements.
  1125. elementAt(counter))) {
  1126. elements.insertElementAt(mapping, counter);
  1127. return;
  1128. }
  1129. }
  1130. }
  1131. elements.addElement(mapping);
  1132. }
  1133. /**
  1134. * Adds <code>parentMapping</code> to <code>styles</code>, and
  1135. * recursively calls this method if <code>parentMapping</code> has
  1136. * any child mappings for any of the Elements in <code>elements</code>.
  1137. */
  1138. private synchronized void getStyles(Object parentMapping,
  1139. Vector styles,
  1140. String[] tags, String[] ids, String[] classes,
  1141. int index, int numElements,
  1142. Hashtable alreadyChecked) {
  1143. // Avoid desending the same mapping twice.
  1144. if (alreadyChecked.contains(parentMapping)) {
  1145. return;
  1146. }
  1147. alreadyChecked.put(parentMapping, parentMapping);
  1148. Style style = getMappingStyle(parentMapping);
  1149. if (style != null) {
  1150. addSortedStyle(parentMapping, styles);
  1151. }
  1152. for (int counter = index; counter < numElements; counter++) {
  1153. String tagString = tags[counter];
  1154. if (tagString != null) {
  1155. Object childMapping = getSelectorMapping(parentMapping,
  1156. tagString, false);
  1157. if (childMapping != null) {
  1158. getStyles(childMapping, styles, tags, ids, classes,
  1159. counter + 1, numElements, alreadyChecked);
  1160. }
  1161. if (classes[counter] != null) {
  1162. String className = classes[counter];
  1163. childMapping = getSelectorMapping(parentMapping,
  1164. tagString + "." + className, false);
  1165. if (childMapping != null) {
  1166. getStyles(childMapping, styles, tags, ids, classes,
  1167. counter + 1, numElements, alreadyChecked);
  1168. }
  1169. childMapping = getSelectorMapping(parentMapping, "." +
  1170. className, false);
  1171. if (childMapping != null) {
  1172. getStyles(childMapping, styles, tags, ids, classes,
  1173. counter + 1, numElements, alreadyChecked);
  1174. }
  1175. }
  1176. if (ids[counter] != null) {
  1177. String idName = ids[counter];
  1178. childMapping = getSelectorMapping(parentMapping,
  1179. tagString + "#" + idName, false);
  1180. if (childMapping != null) {
  1181. getStyles(childMapping, styles, tags, ids, classes,
  1182. counter + 1, numElements, alreadyChecked);
  1183. }
  1184. childMapping = getSelectorMapping(parentMapping, "#" +
  1185. idName, false);
  1186. if (childMapping != null) {
  1187. getStyles(childMapping, styles, tags, ids, classes,
  1188. counter + 1, numElements, alreadyChecked);
  1189. }
  1190. }
  1191. }
  1192. }
  1193. }
  1194. /**
  1195. * Creates a returns Style containing all the rules that match
  1196. * <code>selector</code>.
  1197. */
  1198. private synchronized Style createResolvedStyle(String selector,
  1199. String[] tags,
  1200. String[] ids, String[] classes) {
  1201. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  1202. Vector tempVector = sb.getVector();
  1203. Hashtable tempHashtable = sb.getHashtable();
  1204. // Determine all the Styles that are appropriate, placing them
  1205. // in tempVector
  1206. try {
  1207. Object mapping = getRootSelectorMapping();
  1208. int numElements = tags.length;
  1209. String tagString = tags[0];
  1210. Object childMapping = getSelectorMapping(mapping, tagString,
  1211. false);
  1212. if (childMapping != null) {
  1213. getStyles(childMapping, tempVector, tags, ids, classes, 1,
  1214. numElements, tempHashtable);
  1215. }
  1216. if (classes[0] != null) {
  1217. String className = classes[0];
  1218. childMapping = getSelectorMapping(mapping, tagString + "." +
  1219. className, false);
  1220. if (childMapping != null) {
  1221. getStyles(childMapping, tempVector, tags, ids, classes, 1,
  1222. numElements, tempHashtable);
  1223. }
  1224. childMapping = getSelectorMapping(mapping, "." + className,
  1225. false);
  1226. if (childMapping != null) {
  1227. getStyles(childMapping, tempVector, tags, ids, classes,
  1228. 1, numElements, tempHashtable);
  1229. }
  1230. }
  1231. if (ids[0] != null) {
  1232. String idName = ids[0];
  1233. childMapping = getSelectorMapping(mapping, tagString + "#" +
  1234. idName, false);
  1235. if (childMapping != null) {
  1236. getStyles(childMapping, tempVector, tags, ids, classes,
  1237. 1, numElements, tempHashtable);
  1238. }
  1239. childMapping = getSelectorMapping(mapping, "#" + idName,
  1240. false);
  1241. if (childMapping != null) {
  1242. getStyles(childMapping, tempVector, tags, ids, classes,
  1243. 1, numElements, tempHashtable);
  1244. }
  1245. }
  1246. // Create a new Style that will delegate to all the matching
  1247. // Styles.
  1248. int numLinkedSS = (linkedStyleSheets != null) ?
  1249. linkedStyleSheets.size() : 0;
  1250. int numStyles = tempVector.size();
  1251. AttributeSet[] attrs = new AttributeSet[numStyles + numLinkedSS];
  1252. for (int counter = 0; counter < numStyles; counter++) {
  1253. attrs[counter] = getMappingStyle(tempVector.
  1254. elementAt(counter));
  1255. }
  1256. // Get the AttributeSet from linked style sheets.
  1257. for (int counter = 0; counter < numLinkedSS; counter++) {
  1258. AttributeSet attr = ((StyleSheet)linkedStyleSheets.
  1259. elementAt(counter)).getRule(selector);
  1260. if (attr == null) {
  1261. attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
  1262. }
  1263. else {
  1264. attrs[counter + numStyles] = attr;
  1265. }
  1266. }
  1267. ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
  1268. numStyles);
  1269. resolvedStyles.put(selector, retStyle);
  1270. return retStyle;
  1271. }
  1272. finally {
  1273. SearchBuffer.releaseSearchBuffer(sb);
  1274. }
  1275. }
  1276. /**
  1277. * Creates and returns Style containing all the rules that
  1278. * matches <code>selector</code>. Elements is a
  1279. * Vector of all the Elements the style is being asked for. The
  1280. * first Element is the deepest Element, with the last Element
  1281. * representing the root. <code>t</code> gives the Tag to use for
  1282. * the first Element in <code>elements</code>.
  1283. */
  1284. private Style createResolvedStyle(String selector, Vector elements,
  1285. HTML.Tag t) {
  1286. int numElements = elements.size();
  1287. if (t == HTML.Tag.IMPLIED) {
  1288. t = HTML.Tag.P;
  1289. }
  1290. // Build three arrays, one for tags, one for class's, and one for
  1291. // id's
  1292. String tags[] = new String[numElements];
  1293. String ids[] = new String[numElements];
  1294. String classes[] = new String[numElements];
  1295. for (int counter = 0; counter < numElements; counter++) {
  1296. Element e = (Element)elements.elementAt(counter);
  1297. AttributeSet attr = e.getAttributes();
  1298. if (attr != null) {
  1299. HTML.Tag tag = (HTML.Tag)attr.getAttribute(StyleConstants.
  1300. NameAttribute);
  1301. if (tag == HTML.Tag.IMPLIED) {
  1302. tag = HTML.Tag.P;
  1303. }
  1304. if (tag != null) {
  1305. tags[counter] = tag.toString();
  1306. }
  1307. else {
  1308. tags[counter] = null;
  1309. }
  1310. if (attr.isDefined(HTML.Attribute.CLASS)) {
  1311. classes[counter] = attr.getAttribute
  1312. (HTML.Attribute.CLASS).toString();
  1313. }
  1314. else {
  1315. classes[counter] = null;
  1316. }
  1317. if (attr.isDefined(HTML.Attribute.ID)) {
  1318. ids[counter] = attr.getAttribute(HTML.Attribute.ID).
  1319. toString();
  1320. }
  1321. else {
  1322. ids[counter] = null;
  1323. }
  1324. }
  1325. else {
  1326. tags[counter] = ids[counter] = classes[counter] = null;
  1327. }
  1328. }
  1329. tags[0] = t.toString();
  1330. return createResolvedStyle(selector, tags, ids, classes);
  1331. }
  1332. /**
  1333. * Creates a returns a Style containing all the rules that match
  1334. * <code>selector</code>. It is assumed that each simple selector
  1335. * in <code>selector</code> is separated by a space.
  1336. */
  1337. private Style createResolvedStyle(String selector) {
  1338. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  1339. // Will contain the tags, ids, and classes, in that order.
  1340. Vector elements = sb.getVector();
  1341. try {
  1342. boolean done;
  1343. int dotIndex = 0;
  1344. int spaceIndex = 0;
  1345. int poundIndex = 0;
  1346. int lastIndex = 0;
  1347. int length = selector.length();
  1348. while (lastIndex < length) {
  1349. if (dotIndex == lastIndex) {
  1350. dotIndex = selector.indexOf('.', lastIndex);
  1351. }
  1352. if (poundIndex == lastIndex) {
  1353. poundIndex = selector.indexOf('#', lastIndex);
  1354. }
  1355. spaceIndex = selector.indexOf(' ', lastIndex);
  1356. if (spaceIndex == -1) {
  1357. spaceIndex = length;
  1358. }
  1359. if (dotIndex != -1 && poundIndex != -1 &&
  1360. dotIndex < spaceIndex && poundIndex < spaceIndex) {
  1361. if (poundIndex < dotIndex) {
  1362. // #.
  1363. if (lastIndex == poundIndex) {
  1364. elements.addElement("");
  1365. }
  1366. else {
  1367. elements.addElement(selector.substring(lastIndex,
  1368. poundIndex));
  1369. }
  1370. if ((dotIndex + 1) < spaceIndex) {
  1371. elements.addElement(selector.substring
  1372. (dotIndex + 1, spaceIndex));
  1373. }
  1374. else {
  1375. elements.addElement(null);
  1376. }
  1377. if ((poundIndex + 1) == dotIndex) {
  1378. elements.addElement(null);
  1379. }
  1380. else {
  1381. elements.addElement(selector.substring
  1382. (poundIndex + 1, dotIndex));
  1383. }
  1384. }
  1385. else if(poundIndex < spaceIndex) {
  1386. // .#
  1387. if (lastIndex == dotIndex) {
  1388. elements.addElement("");
  1389. }
  1390. else {
  1391. elements.addElement(selector.substring(lastIndex,
  1392. dotIndex));
  1393. }
  1394. if ((dotIndex + 1) < poundIndex) {
  1395. elements.addElement(selector.substring
  1396. (dotIndex + 1, poundIndex));
  1397. }
  1398. else {
  1399. elements.addElement(null);
  1400. }
  1401. if ((poundIndex + 1) == spaceIndex) {
  1402. elements.addElement(null);
  1403. }
  1404. else {
  1405. elements.addElement(selector.substring
  1406. (poundIndex + 1, spaceIndex));
  1407. }
  1408. }
  1409. dotIndex = poundIndex = spaceIndex + 1;
  1410. }
  1411. else if (dotIndex != -1 && dotIndex < spaceIndex) {
  1412. // .
  1413. if (dotIndex == lastIndex) {
  1414. elements.addElement("");
  1415. }
  1416. else {
  1417. elements.addElement(selector.substring(lastIndex,
  1418. dotIndex));
  1419. }
  1420. if ((dotIndex + 1) == spaceIndex) {
  1421. elements.addElement(null);
  1422. }
  1423. else {
  1424. elements.addElement(selector.substring(dotIndex + 1,
  1425. spaceIndex));
  1426. }
  1427. elements.addElement(null);
  1428. dotIndex = spaceIndex + 1;
  1429. }
  1430. else if (poundIndex != -1 && poundIndex < spaceIndex) {
  1431. // #
  1432. if (poundIndex == lastIndex) {
  1433. elements.addElement("");
  1434. }
  1435. else {
  1436. elements.addElement(selector.substring(lastIndex,
  1437. poundIndex));
  1438. }
  1439. elements.addElement(null);
  1440. if ((poundIndex + 1) == spaceIndex) {
  1441. elements.addElement(null);
  1442. }
  1443. else {
  1444. elements.addElement(selector.substring(poundIndex + 1,
  1445. spaceIndex));
  1446. }
  1447. poundIndex = spaceIndex + 1;
  1448. }
  1449. else {
  1450. // id
  1451. elements.addElement(selector.substring(lastIndex,
  1452. spaceIndex));
  1453. elements.addElement(null);
  1454. elements.addElement(null);
  1455. }
  1456. lastIndex = spaceIndex + 1;
  1457. }
  1458. // Create the tag, id, and class arrays.
  1459. int total = elements.size();
  1460. int numTags = total / 3;
  1461. String[] tags = new String[numTags];
  1462. String[] ids = new String[numTags];
  1463. String[] classes = new String[numTags];
  1464. for (int index = 0, eIndex = total - 3; index < numTags;
  1465. index++, eIndex -= 3) {
  1466. tags[index] = (String)elements.elementAt(eIndex);
  1467. ids[index] = (String)elements.elementAt(eIndex + 1);
  1468. classes[index] = (String)elements.elementAt(eIndex + 2);
  1469. }
  1470. return createResolvedStyle(selector, tags, ids, classes);
  1471. }
  1472. finally {
  1473. SearchBuffer.releaseSearchBuffer(sb);
  1474. }
  1475. }
  1476. /**
  1477. * Should be invoked when a new rule is added that did not previously
  1478. * exist. Will go through and refresh the necessary resovled
  1479. * rules.
  1480. */
  1481. private synchronized void refreshResolvedRules(String selectorName,
  1482. String[] selector,
  1483. Style newStyle,
  1484. int specificity) {
  1485. if (resolvedStyles.size() > 0) {
  1486. Enumeration values = resolvedStyles.elements();
  1487. while (values.hasMoreElements()) {
  1488. ResolvedStyle style = (ResolvedStyle)values.nextElement();
  1489. if (style.matches(selectorName)) {
  1490. style.insertStyle(newStyle, specificity);
  1491. }
  1492. }
  1493. }
  1494. }
  1495. /**
  1496. * A temporary class used to hold a Vector, a StringBuffer and a
  1497. * Hashtable. This is used to avoid allocing a lot of garbage when
  1498. * searching for rules. Use the static method obtainSearchBuffer and
  1499. * releaseSearchBuffer to get a SearchBuffer, and release it when
  1500. * done.
  1501. */
  1502. private static class SearchBuffer {
  1503. /** A stack containing instances of SearchBuffer. Used in getting
  1504. * rules. */
  1505. static Stack searchBuffers = new Stack();
  1506. // A set of temporary variables that can be used in whatever way.
  1507. Vector vector = null;
  1508. StringBuffer stringBuffer = null;
  1509. Hashtable hashtable = null;
  1510. /**
  1511. * Returns an instance of SearchBuffer. Be sure and issue
  1512. * a releaseSearchBuffer when done with it.
  1513. */
  1514. static SearchBuffer obtainSearchBuffer() {
  1515. SearchBuffer sb;
  1516. try {
  1517. sb = (SearchBuffer)searchBuffers.pop();
  1518. } catch (EmptyStackException ese) {
  1519. sb = new SearchBuffer();
  1520. }
  1521. return sb;
  1522. }
  1523. /**
  1524. * Adds <code>sb</code> to the stack of SearchBuffers that can
  1525. * be used.
  1526. */
  1527. static void releaseSearchBuffer(SearchBuffer sb) {
  1528. sb.empty();
  1529. searchBuffers.push(sb);
  1530. }
  1531. StringBuffer getStringBuffer() {
  1532. if (stringBuffer == null) {
  1533. stringBuffer = new StringBuffer();
  1534. }
  1535. return stringBuffer;
  1536. }
  1537. Vector getVector() {
  1538. if (vector == null) {
  1539. vector = new Vector();
  1540. }
  1541. return vector;
  1542. }
  1543. Hashtable getHashtable() {
  1544. if (hashtable == null) {
  1545. hashtable = new Hashtable();
  1546. }
  1547. return hashtable;
  1548. }
  1549. void empty() {
  1550. if (stringBuffer != null) {
  1551. stringBuffer.setLength(0);
  1552. }
  1553. if (vector != null) {
  1554. vector.removeAllElements();
  1555. }
  1556. if (hashtable != null) {
  1557. hashtable.clear();
  1558. }
  1559. }
  1560. }
  1561. static final Border noBorder = new EmptyBorder(0,0,0,0);
  1562. /**
  1563. * Class to carry out some of the duties of
  1564. * css formatting. Implementations of this
  1565. * class enable views to present the css formatting
  1566. * while not knowing anything about how the css values
  1567. * are being cached.
  1568. * <p>
  1569. * As a delegate of Views, this object is responsible for
  1570. * the insets of a View and making sure the background
  1571. * is maintained according to the css attributes.
  1572. */
  1573. public static class BoxPainter implements Serializable {
  1574. BoxPainter(AttributeSet a, CSS css, StyleSheet ss) {
  1575. this.ss = ss;
  1576. this.css = css;
  1577. border = getBorder(a);
  1578. binsets = border.getBorderInsets(null);
  1579. }
  1580. /**
  1581. * Fetch a border to render for the given attributes.
  1582. * PENDING(prinz) This is pretty badly hacked at the
  1583. * moment.
  1584. */
  1585. Border getBorder(AttributeSet a) {
  1586. Border b = noBorder;
  1587. Object o = a.getAttribute(CSS.Attribute.BORDER_STYLE);
  1588. if (o != null) {
  1589. String bstyle = o.toString();
  1590. int bw = (int) getLength(CSS.Attribute.BORDER_WIDTH, a);
  1591. if (bw > 0) {
  1592. if (bstyle.equals("inset")) {
  1593. Color c = getBorderColor(a);
  1594. b = new BevelBorder(BevelBorder.LOWERED, c.brighter(), c.darker());
  1595. } else if (bstyle.equals("outset")) {
  1596. Color c = getBorderColor(a);
  1597. b = new BevelBorder(BevelBorder.RAISED, c.brighter(), c.darker());
  1598. } else if (bstyle.equals("solid")) {
  1599. Color c = getBorderColor(a);
  1600. b = new LineBorder(c);
  1601. }
  1602. }
  1603. }
  1604. return b;
  1605. }
  1606. /**
  1607. * Fetch the color to use for borders. This will either be
  1608. * the value specified by the border-color attribute (which
  1609. * is not inherited), or it will default to the color attribute
  1610. * (which is inherited).
  1611. */
  1612. Color getBorderColor(AttributeSet a) {
  1613. Color color = css.getColor(a, CSS.Attribute.BORDER_COLOR);
  1614. if (color == null) {
  1615. color = css.getColor(a, CSS.Attribute.COLOR);
  1616. if (color == null) {
  1617. return Color.black;
  1618. }
  1619. }
  1620. return color;
  1621. }
  1622. /**
  1623. * Fetchs the inset needed on a given side to
  1624. * account for the margin, border, and padding.
  1625. *
  1626. * @param side The size of the box to fetch the
  1627. * inset for. This can be View.TOP,
  1628. * View.LEFT, View.BOTTOM, or View.RIGHT.
  1629. * @param v the view making the request. This is
  1630. * used to get the AttributeSet, and may be used to
  1631. * resolve percentage arguments.
  1632. * @exception IllegalArgumentException for an invalid direction
  1633. */
  1634. public float getInset(int side, View v) {
  1635. AttributeSet a = v.getAttributes();
  1636. float inset = 0;
  1637. switch(side) {
  1638. case View.LEFT:
  1639. inset += getLength(CSS.Attribute.MARGIN_LEFT, a);
  1640. inset += binsets.left;
  1641. inset += getLength(CSS.Attribute.PADDING_LEFT, a);
  1642. break;
  1643. case View.RIGHT:
  1644. inset += getLength(CSS.Attribute.MARGIN_RIGHT, a);
  1645. inset += binsets.right;
  1646. inset += getLength(CSS.Attribute.PADDING_RIGHT, a);
  1647. break;
  1648. case View.TOP:
  1649. inset += getLength(CSS.Attribute.MARGIN_TOP, a);
  1650. inset += binsets.top;
  1651. inset += getLength(CSS.Attribute.PADDING_TOP, a);
  1652. break;
  1653. case View.BOTTOM:
  1654. inset += getLength(CSS.Attribute.MARGIN_BOTTOM, a);
  1655. inset += binsets.bottom;
  1656. inset += getLength(CSS.Attribute.PADDING_BOTTOM, a);
  1657. break;
  1658. default:
  1659. throw new IllegalArgumentException("Invalid side: " + side);
  1660. }
  1661. return inset;
  1662. }
  1663. /**
  1664. * Paints the css box according to the attributes
  1665. * given. This should paint the border, padding,
  1666. * and background.
  1667. *
  1668. * @param g the rendering surface.
  1669. * @param x the x coordinate of the allocated area to
  1670. * render into.
  1671. * @param y the y coordinate of the allocated area to
  1672. * render into.
  1673. * @param w the width of the allocated area to render into.
  1674. * @param h the height of the allocated area to render into.
  1675. * @param v the view making the request. This is
  1676. * used to get the AttributeSet, and may be used to
  1677. * resolve percentage arguments.
  1678. */
  1679. public void paint(Graphics g, float x, float y, float w, float h, View v) {
  1680. AttributeSet a = v.getAttributes();
  1681. // PENDING(prinz) implement real rendering... which would
  1682. // do full set of border and background capabilities.
  1683. StyleSheet sheet = ss;
  1684. // remove margin
  1685. float top = getLength(CSS.Attribute.MARGIN_TOP, a);
  1686. float left = getLength(CSS.Attribute.MARGIN_LEFT, a);
  1687. x += left;
  1688. y += top;
  1689. w -= left + getLength(CSS.Attribute.MARGIN_RIGHT, a);
  1690. h -= top + getLength(CSS.Attribute.MARGIN_BOTTOM, a);
  1691. Color bg;
  1692. if (sheet != null) {
  1693. bg = sheet.getBackground(a);
  1694. }
  1695. else {
  1696. bg = null;
  1697. }
  1698. if (bg != null) {
  1699. g.setColor(bg);
  1700. g.fillRect((int) x, (int) y, (int) w, (int) h);
  1701. }
  1702. border.paintBorder(null, g, (int) x, (int) y, (int) w, (int) h);
  1703. }
  1704. float getLength(CSS.Attribute key, AttributeSet a) {
  1705. return css.getLength(a, key);
  1706. }
  1707. Border border;
  1708. Insets binsets;
  1709. CSS css;
  1710. StyleSheet ss;
  1711. }
  1712. /**
  1713. * class to carry out some of the duties of css list
  1714. * formatting. Implementations of this
  1715. * class enable views to present the css formatting
  1716. * while not knowing anything about how the css values
  1717. * are being cached.
  1718. */
  1719. public static class ListPainter implements Serializable {
  1720. ListPainter(AttributeSet attr) {
  1721. /* Get the image to use as a list bullet */
  1722. String imgstr = (String)attr.getAttribute(CSS.Attribute.
  1723. LIST_STYLE_IMAGE);
  1724. type = null;
  1725. if (imgstr != null && !imgstr.equals("none")) {
  1726. try {
  1727. String tmpstr = null;
  1728. StringTokenizer st = new StringTokenizer(imgstr, "()");
  1729. if (st.hasMoreTokens())
  1730. tmpstr = st.nextToken();
  1731. if (st.hasMoreTokens())
  1732. tmpstr = st.nextToken();
  1733. URL u = new URL(tmpstr);
  1734. img = new ImageIcon(u);
  1735. } catch (MalformedURLException e) {
  1736. type = null;
  1737. img = null;
  1738. }
  1739. }
  1740. /* Get the type of bullet to use in the list */
  1741. if (img == null) {
  1742. type = (CSS.Value)attr.getAttribute(CSS.Attribute.
  1743. LIST_STYLE_TYPE);
  1744. }
  1745. // PENDING(sky): Resolve this, there is no way to get the
  1746. // start value. start is an HTML attribute, and not visible
  1747. // from attr.
  1748. start = 1;
  1749. }
  1750. /**
  1751. * Returns a string that represents the value
  1752. * of the HTML.Attribute.TYPE attribute.
  1753. * If this attributes is not defined, then
  1754. * then the type defaults to "disc" unless
  1755. * the tag is on Ordered list. In the case
  1756. * of the latter, the default type is "decimal".
  1757. */
  1758. private CSS.Value getChildType(View childView) {
  1759. CSS.Value childtype = (CSS.Value)childView.getAttributes().
  1760. getAttribute(CSS.Attribute.LIST_STYLE_TYPE);
  1761. if (childtype == null) {
  1762. if (type == null) {
  1763. // Parent view.
  1764. View v = childView.getParent();
  1765. HTMLDocument doc = (HTMLDocument)v.getDocument();
  1766. if (doc.matchNameAttribute(v.getElement().getAttributes(),
  1767. HTML.Tag.OL)) {
  1768. childtype = CSS.Value.DECIMAL;
  1769. } else {
  1770. childtype = CSS.Value.DISC;
  1771. }
  1772. } else {
  1773. childtype = type;
  1774. }
  1775. }
  1776. return childtype;
  1777. }
  1778. /**
  1779. * Returns an integer that should be used to render the child at
  1780. * <code>childIndex</code> with. The retValue will usually be
  1781. * <code>childIndex</code> + 1, unless <code>parentView</code>
  1782. * has some Views that do not represent LI's, or one of the views
  1783. * has a HTML.Attribute.START specified.
  1784. */
  1785. private int getRenderIndex(View parentView, int childIndex) {
  1786. int retIndex = childIndex;
  1787. for (int counter = childIndex; counter >= 0; counter--) {
  1788. AttributeSet as = parentView.getElement().getElement(counter).
  1789. getAttributes();
  1790. if (as.getAttribute(StyleConstants.NameAttribute) !=
  1791. HTML.Tag.LI) {
  1792. retIndex--;
  1793. }
  1794. else {
  1795. Object value = as.getAttribute(HTML.Attribute.VALUE);
  1796. if (value != null &&
  1797. (value instanceof String)) {
  1798. try {
  1799. int iValue = Integer.parseInt((String)value);
  1800. return retIndex - counter + iValue;
  1801. }
  1802. catch (NumberFormatException nfe) {}
  1803. }
  1804. }
  1805. }
  1806. return retIndex + start;
  1807. }
  1808. /**
  1809. * Paints the css list decoration according to the
  1810. * attributes given.
  1811. *
  1812. * @param g the rendering surface.
  1813. * @param x the x coordinate of the list item allocation
  1814. * @param y the y coordinate of the list item allocation
  1815. * @param w the width of the list item allocation
  1816. * @param h the height of the list item allocation
  1817. * @param s the allocated area to paint into.
  1818. * @param item which list item is being painted. This
  1819. * is a number >= 0.
  1820. */
  1821. public void paint(Graphics g, float x, float y, float w, float h, View v, int item) {
  1822. View cv = v.getView(item);
  1823. Object name = cv.getElement().getAttributes().getAttribute
  1824. (StyleConstants.NameAttribute);
  1825. // Only draw something if the View is a list item. This won't
  1826. // be the case for comments.
  1827. if (!(name instanceof HTML.Tag) ||
  1828. name != HTML.Tag.LI) {
  1829. return;
  1830. }
  1831. CSS.Value childtype = getChildType(cv);
  1832. float align = cv.getAlignment(View.Y_AXIS);
  1833. Font font = ((StyledDocument)cv.getDocument()).
  1834. getFont(cv.getAttributes());
  1835. if (font != null) {
  1836. g.setFont(font);
  1837. }
  1838. if (childtype == CSS.Value.SQUARE || childtype == CSS.Value.CIRCLE
  1839. || childtype == CSS.Value.DISC) {
  1840. drawShape(g, childtype, (int) x, (int) y, (int) h, align);
  1841. } else if (childtype == CSS.Value.CIRCLE) {
  1842. drawShape(g, childtype, (int) x, (int) y, (int) h, align);
  1843. } else if (childtype == CSS.Value.DECIMAL) {
  1844. drawLetter(g, '1', (int) x, (int) y, (int) h,
  1845. getRenderIndex(v, item));
  1846. } else if (childtype == CSS.Value.LOWER_ALPHA) {
  1847. drawLetter(g, 'a', (int) x, (int) y, (int) h,
  1848. getRenderIndex(v, item));
  1849. } else if (childtype == CSS.Value.UPPER_ALPHA) {
  1850. drawLetter(g, 'A', (int) x, (int) y, (int) h,
  1851. getRenderIndex(v, item));
  1852. } else if (childtype == CSS.Value.LOWER_ROMAN) {
  1853. drawLetter(g, 'i', (int) x, (int) y, (int) h,
  1854. getRenderIndex(v, item));
  1855. } else if (childtype == CSS.Value.UPPER_ROMAN) {
  1856. drawLetter(g, 'I', (int) x, (int) y, (int) h,
  1857. getRenderIndex(v, item));
  1858. } else if (childtype == null && img != null) {
  1859. drawIcon(g, (int) x, (int) y, (int) h, align,
  1860. v.getContainer());
  1861. }
  1862. }
  1863. /**
  1864. * Draws the bullet icon specified by the list-style-image argument.
  1865. *
  1866. * @param g the graphics context
  1867. * @param ax x coordinate to place the bullet
  1868. * @param ay y coordinate to place the bullet
  1869. * @param ah height of the container the bullet is placed in
  1870. * @param align preferred alignment factor for the child view
  1871. */
  1872. void drawIcon(Graphics g, int ax, int ay, int ah,
  1873. float align, Component c) {
  1874. g.setColor(Color.black);
  1875. int x = ax - img.getIconWidth() - bulletgap;
  1876. int y = ay + (int)(ah * align) - 3;
  1877. img.paintIcon(c, g, x, y);
  1878. }
  1879. /**
  1880. * Draws the graphical bullet item specified by the type argument.
  1881. *
  1882. * @param g the graphics context
  1883. * @param type type of bullet to draw (circle, square, disc)
  1884. * @param ax x coordinate to place the bullet
  1885. * @param ay y coordinate to place the bullet
  1886. * @param ah height of the container the bullet is placed in
  1887. * @param align preferred alignment factor for the child view
  1888. */
  1889. void drawShape(Graphics g, CSS.Value type, int ax, int ay, int ah,
  1890. float align) {
  1891. g.setColor(Color.black);
  1892. int x = ax - bulletgap - 7;
  1893. int y = ay + (int)(ah * align) - 3;
  1894. if (type == CSS.Value.SQUARE) {
  1895. g.drawRect(x, y, 7, 7);
  1896. } else if (type == CSS.Value.CIRCLE) {
  1897. g.drawOval(x, y, 7, 7);
  1898. } else {
  1899. g.fillOval(x, y, 7, 7);
  1900. }
  1901. }
  1902. /**
  1903. * Draws the letter or number for an ordered list.
  1904. *
  1905. * @param g the graphics context
  1906. * @param letter type of ordered list to draw
  1907. * @param ax x coordinate to place the bullet
  1908. * @param ay y coordinate to place the bullet
  1909. * @param ah height of the container the bullet is placed in
  1910. * @param index position of the list item in the list
  1911. */
  1912. void drawLetter(Graphics g, char letter, int ax, int ay, int ah,
  1913. int index) {
  1914. g.setColor(Color.black);
  1915. String str = formatItemNum(index, letter) + ".";
  1916. FontMetrics fm = g.getFontMetrics();
  1917. int stringwidth = fm.stringWidth(str);
  1918. int x = ax - stringwidth - bulletgap;
  1919. int y = ay + fm.getAscent() + fm.getLeading();
  1920. g.drawString(str, x, y);
  1921. }
  1922. /**
  1923. * Converts the item number into the ordered list number
  1924. * (i.e. 1 2 3, i ii iii, a b c, etc.
  1925. *
  1926. * @param itemNum number to format
  1927. * @param type type of ordered list
  1928. */
  1929. String formatItemNum(int itemNum, char type) {
  1930. String numStyle = "1";
  1931. boolean uppercase = false;
  1932. String formattedNum;
  1933. switch (type) {
  1934. case '1':
  1935. default:
  1936. formattedNum = String.valueOf(itemNum);
  1937. break;
  1938. case 'A':
  1939. uppercase = true;
  1940. // fall through
  1941. case 'a':
  1942. formattedNum = formatAlphaNumerals(itemNum);
  1943. break;
  1944. case 'I':
  1945. uppercase = true;
  1946. // fall through
  1947. case 'i':
  1948. formattedNum = formatRomanNumerals(itemNum);
  1949. }
  1950. if (uppercase) {
  1951. formattedNum = formattedNum.toUpperCase();
  1952. }
  1953. return formattedNum;
  1954. }
  1955. /**
  1956. * Converts the item number into an alphabetic character
  1957. *
  1958. * @param itemNum number to format
  1959. */
  1960. String formatAlphaNumerals(int itemNum) {
  1961. String result = "";
  1962. if (itemNum > 26) {
  1963. result = formatAlphaNumerals(itemNum / 26) +
  1964. formatAlphaNumerals(itemNum % 26);
  1965. } else {
  1966. // -1 because item is 1 based.
  1967. result = String.valueOf((char)('a' + itemNum - 1));
  1968. }
  1969. return result;
  1970. }
  1971. /* list of roman numerals */
  1972. static final char romanChars[][] = {
  1973. {'i', 'v'},
  1974. {'x', 'l' },
  1975. {'c', 'd' },
  1976. {'m', '?' },
  1977. };
  1978. /**
  1979. * Converts the item number into a roman numeral
  1980. *
  1981. * @param num number to format
  1982. */
  1983. String formatRomanNumerals(int num) {
  1984. return formatRomanNumerals(0, num);
  1985. }
  1986. /**
  1987. * Converts the item number into a roman numeral
  1988. *
  1989. * @param num number to format
  1990. */
  1991. String formatRomanNumerals(int level, int num) {
  1992. if (num < 10) {
  1993. return formatRomanDigit(level, num);
  1994. } else {
  1995. return formatRomanNumerals(level + 1, num / 10) +
  1996. formatRomanDigit(level, num % 10);
  1997. }
  1998. }
  1999. /**
  2000. * Converts the item number into a roman numeral
  2001. *
  2002. * @param level position
  2003. * @param num digit to format
  2004. */
  2005. String formatRomanDigit(int level, int digit) {
  2006. String result = "";
  2007. if (digit == 9) {
  2008. result = result + romanChars[level][0];
  2009. result = result + romanChars[level + 1][0];
  2010. return result;
  2011. } else if (digit == 4) {
  2012. result = result + romanChars[level][0];
  2013. result = result + romanChars[level][1];
  2014. return result;
  2015. } else if (digit >= 5) {
  2016. result = result + romanChars[level][1];
  2017. digit -= 5;
  2018. }
  2019. for (int i = 0; i < digit; i++) {
  2020. result = result + romanChars[level][0];
  2021. }
  2022. return result;
  2023. }
  2024. private int start;
  2025. private CSS.Value type;
  2026. URL imageurl;
  2027. Icon img = null;
  2028. private int bulletgap = 5;
  2029. }
  2030. /**
  2031. * An implementation of AttributeSet that can multiplex
  2032. * across a set of AttributeSets.
  2033. */
  2034. static class MuxingAttributeSet implements AttributeSet {
  2035. MuxingAttributeSet(AttributeSet[] attrs) {
  2036. this.attrs = attrs;
  2037. }
  2038. MuxingAttributeSet() {
  2039. }
  2040. protected synchronized void setAttributes(AttributeSet[] attrs) {
  2041. this.attrs = attrs;
  2042. }
  2043. /**
  2044. * Returns the AttributeSets multiplexing too. When the AttributeSets
  2045. * need to be referenced, this should be called.
  2046. */
  2047. protected synchronized AttributeSet[] getAttributes() {
  2048. return attrs;
  2049. }
  2050. /**
  2051. * Inserts <code>as</code> at <code>index</code>. This assumes
  2052. * <code>index</code> is >= 0 && <= attrs.length.
  2053. */
  2054. protected synchronized void insertAttributeSetAt(AttributeSet as,
  2055. int index) {
  2056. int numAttrs = attrs.length;
  2057. AttributeSet newAttrs[] = new AttributeSet[numAttrs + 1];
  2058. if (index < numAttrs) {
  2059. if (index > 0) {
  2060. System.arraycopy(attrs, 0, newAttrs, 0, index);
  2061. System.arraycopy(attrs, index, newAttrs, index + 1,
  2062. numAttrs - index);
  2063. }
  2064. else {
  2065. System.arraycopy(attrs, 0, newAttrs, 1, numAttrs);
  2066. }
  2067. }
  2068. else {
  2069. System.arraycopy(attrs, 0, newAttrs, 0, numAttrs);
  2070. }
  2071. newAttrs[index] = as;
  2072. attrs = newAttrs;
  2073. }
  2074. /**
  2075. * Removes the AttributeSet at <code>index</code>. This assumes
  2076. * <code>index</code> is >= 0 && < attrs.length
  2077. */
  2078. protected synchronized void removeAttributeSetAt(int index) {
  2079. int numAttrs = attrs.length;
  2080. AttributeSet[] newAttrs = new AttributeSet[numAttrs - 1];
  2081. if (numAttrs > 0) {
  2082. if (index == 0) {
  2083. // FIRST
  2084. System.arraycopy(attrs, 1, newAttrs, 0, numAttrs - 1);
  2085. }
  2086. else if (index < (numAttrs - 1)) {
  2087. // MIDDLE
  2088. System.arraycopy(attrs, 0, newAttrs, 0, index);
  2089. System.arraycopy(attrs, index + 1, newAttrs, index,
  2090. numAttrs - index - 1);
  2091. }
  2092. else {
  2093. // END
  2094. System.arraycopy(attrs, 0, newAttrs, 0, numAttrs - 1);
  2095. }
  2096. }
  2097. attrs = newAttrs;
  2098. }
  2099. // --- AttributeSet methods ----------------------------
  2100. /**
  2101. * Gets the number of attributes that are defined.
  2102. *
  2103. * @return the number of attributes
  2104. * @see AttributeSet#getAttributeCount
  2105. */
  2106. public int getAttributeCount() {
  2107. AttributeSet[] as = getAttributes();
  2108. int n = 0;
  2109. for (int i = 0; i < as.length; i++) {
  2110. n += as[i].getAttributeCount();
  2111. }
  2112. return n;
  2113. }
  2114. /**
  2115. * Checks whether a given attribute is defined.
  2116. * This will convert the key over to CSS if the
  2117. * key is a StyleConstants key that has a CSS
  2118. * mapping.
  2119. *
  2120. * @param key the attribute key
  2121. * @return true if the attribute is defined
  2122. * @see AttributeSet#isDefined
  2123. */
  2124. public boolean isDefined(Object key) {
  2125. AttributeSet[] as = getAttributes();
  2126. for (int i = 0; i < as.length; i++) {
  2127. if (as[i].isDefined(key)) {
  2128. return true;
  2129. }
  2130. }
  2131. return false;
  2132. }
  2133. /**
  2134. * Checks whether two attribute sets are equal.
  2135. *
  2136. * @param attr the attribute set to check against
  2137. * @return true if the same
  2138. * @see AttributeSet#isEqual
  2139. */
  2140. public boolean isEqual(AttributeSet attr) {
  2141. return ((getAttributeCount() == attr.getAttributeCount()) &&
  2142. containsAttributes(attr));
  2143. }
  2144. /**
  2145. * Copies a set of attributes.
  2146. *
  2147. * @return the copy
  2148. * @see AttributeSet#copyAttributes
  2149. */
  2150. public AttributeSet copyAttributes() {
  2151. AttributeSet[] as = getAttributes();
  2152. MutableAttributeSet a = new SimpleAttributeSet();
  2153. int n = 0;
  2154. for (int i = as.length - 1; i >= 0; i--) {
  2155. a.addAttributes(as[i]);
  2156. }
  2157. return a;
  2158. }
  2159. /**
  2160. * Gets the value of an attribute. If the requested
  2161. * attribute is a StyleConstants attribute that has
  2162. * a CSS mapping, the request will be converted.
  2163. *
  2164. * @param key the attribute name
  2165. * @return the attribute value
  2166. * @see AttributeSet#getAttribute
  2167. */
  2168. public Object getAttribute(Object key) {
  2169. AttributeSet[] as = getAttributes();
  2170. int n = as.length;
  2171. for (int i = 0; i < n; i++) {
  2172. Object o = as[i].getAttribute(key);
  2173. if (o != null) {
  2174. return o;
  2175. }
  2176. }
  2177. return null;
  2178. }
  2179. /**
  2180. * Gets the names of all attributes.
  2181. *
  2182. * @return the attribute names
  2183. * @see AttributeSet#getAttributeNames
  2184. */
  2185. public Enumeration getAttributeNames() {
  2186. return new MuxingAttributeNameEnumeration();
  2187. }
  2188. /**
  2189. * Checks whether a given attribute name/value is defined.
  2190. *
  2191. * @param name the attribute name
  2192. * @param value the attribute value
  2193. * @return true if the name/value is defined
  2194. * @see AttributeSet#containsAttribute
  2195. */
  2196. public boolean containsAttribute(Object name, Object value) {
  2197. return value.equals(getAttribute(name));
  2198. }
  2199. /**
  2200. * Checks whether the attribute set contains all of
  2201. * the given attributes.
  2202. *
  2203. * @param attrs the attributes to check
  2204. * @return true if the element contains all the attributes
  2205. * @see AttributeSet#containsAttributes
  2206. */
  2207. public boolean containsAttributes(AttributeSet attrs) {
  2208. boolean result = true;
  2209. Enumeration names = attrs.getAttributeNames();
  2210. while (result && names.hasMoreElements()) {
  2211. Object name = names.nextElement();
  2212. result = attrs.getAttribute(name).equals(getAttribute(name));
  2213. }
  2214. return result;
  2215. }
  2216. /**
  2217. * Returns null, subclasses may wish to do something more
  2218. * intelligent with this.
  2219. */
  2220. public AttributeSet getResolveParent() {
  2221. return null;
  2222. }
  2223. AttributeSet[] attrs;
  2224. /**
  2225. * An Enumeration of the Attribute names in a MuxingAttributeSet.
  2226. * This may return the same name more than once.
  2227. */
  2228. class MuxingAttributeNameEnumeration implements Enumeration {
  2229. MuxingAttributeNameEnumeration() {
  2230. updateEnum();
  2231. }
  2232. public boolean hasMoreElements() {
  2233. if (currentEnum == null) {
  2234. return false;
  2235. }
  2236. return currentEnum.hasMoreElements();
  2237. }
  2238. public Object nextElement() {
  2239. if (currentEnum == null) {
  2240. throw new NoSuchElementException("No more names");
  2241. }
  2242. Object retObject = currentEnum.nextElement();
  2243. if (!currentEnum.hasMoreElements()) {
  2244. updateEnum();
  2245. }
  2246. return retObject;
  2247. }
  2248. void updateEnum() {
  2249. AttributeSet[] as = getAttributes();
  2250. currentEnum = null;
  2251. while (currentEnum == null && attrIndex < as.length) {
  2252. currentEnum = as[attrIndex++].getAttributeNames();
  2253. if (!currentEnum.hasMoreElements()) {
  2254. currentEnum = null;
  2255. }
  2256. }
  2257. }
  2258. /** Index into attrs the current Enumeration came from. */
  2259. int attrIndex;
  2260. /** Enumeration from attrs. */
  2261. Enumeration currentEnum;
  2262. }
  2263. }
  2264. /**
  2265. * A subclass of MuxingAttributeSet that translates between
  2266. * CSS and HTML and StyleConstants. The AttributeSets used are
  2267. * the CSS rules that match the Views Elements.
  2268. */
  2269. class ViewAttributeSet extends MuxingAttributeSet {
  2270. ViewAttributeSet(View v) {
  2271. host = v;
  2272. // PENDING(prinz) fix this up to be a more realistic
  2273. // implementation.
  2274. Document doc = v.getDocument();
  2275. SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
  2276. Vector muxList = sb.getVector();
  2277. try {
  2278. if (doc instanceof HTMLDocument) {
  2279. StyleSheet styles = StyleSheet.this;
  2280. Element elem = v.getElement();
  2281. AttributeSet a = elem.getAttributes();
  2282. AttributeSet htmlAttr = styles.translateHTMLToCSS(a);
  2283. if (htmlAttr.getAttributeCount() != 0) {
  2284. muxList.addElement(htmlAttr);
  2285. }
  2286. if (elem.isLeaf()) {
  2287. Enumeration keys = a.getAttributeNames();
  2288. while (keys.hasMoreElements()) {
  2289. Object key = keys.nextElement();
  2290. if (key instanceof HTML.Tag) {
  2291. if ((HTML.Tag)key == HTML.Tag.A) {
  2292. Object o = a.getAttribute((HTML.Tag)key);
  2293. /**
  2294. In the case of an A tag, the css rules
  2295. apply only for tags that have their
  2296. href attribute defined and not for
  2297. anchors that only have their name attributes
  2298. defined, i.e anchors that function as
  2299. destinations. Hence we do not add the
  2300. attributes for that latter kind of
  2301. anchors. When CSS2 support is added,
  2302. it will be possible to specificity this
  2303. kind of conditional behaviour in the
  2304. stylesheet.
  2305. **/
  2306. if (o != null && o instanceof AttributeSet) {
  2307. AttributeSet attr = (AttributeSet)o;
  2308. if (attr.getAttribute(HTML.Attribute.HREF) == null) {
  2309. continue;
  2310. }
  2311. }
  2312. }
  2313. AttributeSet cssRule = styles.getRule((HTML.Tag) key, elem);
  2314. if (cssRule != null) {
  2315. muxList.addElement(cssRule);
  2316. }
  2317. }
  2318. }
  2319. } else {
  2320. HTML.Tag t = (HTML.Tag) a.getAttribute
  2321. (StyleConstants.NameAttribute);
  2322. if (t == HTML.Tag.IMPLIED) {
  2323. t = HTML.Tag.P;
  2324. }
  2325. AttributeSet cssRule = styles.getRule(t, elem);
  2326. if (cssRule != null) {
  2327. muxList.addElement(cssRule);
  2328. }
  2329. }
  2330. }
  2331. this.attrs = new AttributeSet[muxList.size()];
  2332. muxList.copyInto(this.attrs);
  2333. }
  2334. finally {
  2335. SearchBuffer.releaseSearchBuffer(sb);
  2336. }
  2337. }
  2338. // --- AttributeSet methods ----------------------------
  2339. /**
  2340. * Checks whether a given attribute is defined.
  2341. * This will convert the key over to CSS if the
  2342. * key is a StyleConstants key that has a CSS
  2343. * mapping.
  2344. *
  2345. * @param key the attribute key
  2346. * @return true if the attribute is defined
  2347. * @see AttributeSet#isDefined
  2348. */
  2349. public boolean isDefined(Object key) {
  2350. if (key instanceof StyleConstants) {
  2351. Object cssKey = css.styleConstantsKeyToCSSKey
  2352. ((StyleConstants)key);
  2353. if (cssKey != null) {
  2354. key = cssKey;
  2355. }
  2356. }
  2357. return super.isDefined(key);
  2358. }
  2359. /**
  2360. * Gets the value of an attribute. If the requested
  2361. * attribute is a StyleConstants attribute that has
  2362. * a CSS mapping, the request will be converted.
  2363. *
  2364. * @param key the attribute name
  2365. * @return the attribute value
  2366. * @see AttributeSet#getAttribute
  2367. */
  2368. public Object getAttribute(Object key) {
  2369. if (key instanceof StyleConstants) {
  2370. Object cssKey = css.styleConstantsKeyToCSSKey
  2371. ((StyleConstants)key);
  2372. if (cssKey != null) {
  2373. Object value = doGetAttribute(cssKey);
  2374. if (value instanceof CSS.CssValue) {
  2375. return ((CSS.CssValue)value).toStyleConstants
  2376. ((StyleConstants)key);
  2377. }
  2378. }
  2379. }
  2380. return doGetAttribute(key);
  2381. }
  2382. Object doGetAttribute(Object key) {
  2383. Object retValue = super.getAttribute(key);
  2384. if (retValue != null) {
  2385. return retValue;
  2386. }
  2387. // didn't find it... try parent if it's a css attribute
  2388. // that is inherited.
  2389. if (key instanceof CSS.Attribute) {
  2390. CSS.Attribute css = (CSS.Attribute) key;
  2391. if (css.isInherited()) {
  2392. AttributeSet parent = getResolveParent();
  2393. if (parent != null)
  2394. return parent.getAttribute(key);
  2395. }
  2396. }
  2397. return null;
  2398. }
  2399. /**
  2400. * If not overriden, the resolving parent defaults to
  2401. * the parent element.
  2402. *
  2403. * @return the attributes from the parent
  2404. * @see AttributeSet#getResolveParent
  2405. */
  2406. public AttributeSet getResolveParent() {
  2407. if (host == null) {
  2408. return null;
  2409. }
  2410. View parent = host.getParent();
  2411. return (parent != null) ? parent.getAttributes() : null;
  2412. }
  2413. /** View created for. */
  2414. View host;
  2415. }
  2416. /**
  2417. * A subclass of MuxingAttributeSet that implements Style. Currently
  2418. * the MutableAttributeSet methods are unimplemented, that is they
  2419. * do nothing.
  2420. */
  2421. // PENDING(sky): Decide what to do with this. Either make it
  2422. // contain a SimpleAttributeSet that modify methods are delegated to,
  2423. // or change getRule to return an AttributeSet and then don't make this
  2424. // implement Style.
  2425. static class ResolvedStyle extends MuxingAttributeSet implements
  2426. Style {
  2427. ResolvedStyle(String name, AttributeSet[] attrs, int extendedIndex) {
  2428. this.attrs = attrs;
  2429. this.name = name;
  2430. this.extendedIndex = extendedIndex;
  2431. }
  2432. /**
  2433. * Inserts a Style into the receiver so that the styles the
  2434. * receiver represents are still ordered by specificity.
  2435. * <code>style</code> will be added before any extended styles, that
  2436. * is before extendedIndex.
  2437. */
  2438. synchronized void insertStyle(Style style, int specificity) {
  2439. int maxCounter = attrs.length;
  2440. int counter = 0;
  2441. for (;counter < extendedIndex; counter++) {
  2442. if (specificity > getSpecificity(((Style)attrs[counter]).
  2443. getName())) {
  2444. break;
  2445. }
  2446. }
  2447. insertAttributeSetAt(style, counter);
  2448. extendedIndex++;
  2449. }
  2450. /**
  2451. * Removes a previously added style. This will do nothing if
  2452. * <code>style</code> is not referenced by the receiver.
  2453. */
  2454. synchronized void removeStyle(Style style) {
  2455. for (int counter = attrs.length - 1; counter >= 0; counter--) {
  2456. if (attrs[counter] == style) {
  2457. removeAttributeSetAt(counter);
  2458. if (counter < extendedIndex) {
  2459. extendedIndex--;
  2460. }
  2461. break;
  2462. }
  2463. }
  2464. }
  2465. /**
  2466. * Adds <code>s</code> as one of the Attributesets to look up
  2467. * attributes in.
  2468. */
  2469. synchronized void insertExtendedStyleAt(Style attr, int index) {
  2470. insertAttributeSetAt(attr, extendedIndex + index);
  2471. }
  2472. /**
  2473. * Adds <code>s</code> as one of the Attributesets to look up
  2474. * attributes in. It will be the AttributeSet last checked.
  2475. */
  2476. synchronized void addExtendedStyle(Style attr) {
  2477. insertAttributeSetAt(attr, attrs.length);
  2478. }
  2479. /**
  2480. * Removes the style at <code>index</code> +
  2481. * <code>extendedIndex</code>.
  2482. */
  2483. synchronized void removeExtendedStyleAt(int index) {
  2484. removeAttributeSetAt(extendedIndex + index);
  2485. }
  2486. /**
  2487. * Returns true if the receiver matches <code>selector</code>, where
  2488. * a match is defined by the CSS rule matching.
  2489. * Each simple selector must be separated by a single space.
  2490. */
  2491. protected boolean matches(String selector) {
  2492. int sLast = selector.length();
  2493. if (sLast == 0) {
  2494. return false;
  2495. }
  2496. int thisLast = name.length();
  2497. int sCurrent = selector.lastIndexOf(' ');
  2498. int thisCurrent = name.lastIndexOf(' ');
  2499. if (sCurrent >= 0) {
  2500. sCurrent++;
  2501. }
  2502. if (thisCurrent >= 0) {
  2503. thisCurrent++;
  2504. }
  2505. if (!matches(selector, sCurrent, sLast, thisCurrent, thisLast)) {
  2506. return false;
  2507. }
  2508. while (sCurrent != -1) {
  2509. sLast = sCurrent - 1;
  2510. sCurrent = selector.lastIndexOf(' ', sLast - 1);
  2511. if (sCurrent >= 0) {
  2512. sCurrent++;
  2513. }
  2514. boolean match = false;
  2515. while (!match && thisCurrent != -1) {
  2516. thisLast = thisCurrent - 1;
  2517. thisCurrent = name.lastIndexOf(' ', thisLast - 1);
  2518. if (thisCurrent >= 0) {
  2519. thisCurrent++;
  2520. }
  2521. match = matches(selector, sCurrent, sLast, thisCurrent,
  2522. thisLast);
  2523. }
  2524. if (!match) {
  2525. return false;
  2526. }
  2527. }
  2528. return true;
  2529. }
  2530. /**
  2531. * Returns true if the substring of the receiver, in the range
  2532. * thisCurrent, thisLast matches the substring of selector in
  2533. * the ranme sCurrent to sLast based on CSS selector matching.
  2534. */
  2535. boolean matches(String selector, int sCurrent, int sLast,
  2536. int thisCurrent, int thisLast) {
  2537. sCurrent = Math.max(sCurrent, 0);
  2538. thisCurrent = Math.max(thisCurrent, 0);
  2539. int thisDotIndex = boundedIndexOf(name, '.', thisCurrent,
  2540. thisLast);
  2541. int thisPoundIndex = boundedIndexOf(name, '#', thisCurrent,
  2542. thisLast);
  2543. int sDotIndex = boundedIndexOf(selector, '.', sCurrent, sLast);
  2544. int sPoundIndex = boundedIndexOf(selector, '#', sCurrent, sLast);
  2545. if (sDotIndex != -1) {
  2546. // Selector has a '.', which indicates name must match it,
  2547. // or if the '.' starts the selector than name must have
  2548. // the same class (doesn't matter what element name).
  2549. if (thisDotIndex == -1) {
  2550. return false;
  2551. }
  2552. if (sCurrent == sDotIndex) {
  2553. if ((thisLast - thisDotIndex) != (sLast - sDotIndex) ||
  2554. !selector.regionMatches(sCurrent, name, thisDotIndex,
  2555. (thisLast - thisDotIndex))) {
  2556. return false;
  2557. }
  2558. }
  2559. else {
  2560. // Has to fully match.
  2561. if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
  2562. !selector.regionMatches(sCurrent, name, thisCurrent,
  2563. (thisLast - thisCurrent))) {
  2564. return false;
  2565. }
  2566. }
  2567. return true;
  2568. }
  2569. if (sPoundIndex != -1) {
  2570. // Selector has a '#', which indicates name must match it,
  2571. // or if the '#' starts the selector than name must have
  2572. // the same id (doesn't matter what element name).
  2573. if (thisPoundIndex == -1) {
  2574. return false;
  2575. }
  2576. if (sCurrent == sPoundIndex) {
  2577. if ((thisLast - thisPoundIndex) !=(sLast - sPoundIndex) ||
  2578. !selector.regionMatches(sCurrent, name, thisPoundIndex,
  2579. (thisLast - thisPoundIndex))) {
  2580. return false;
  2581. }
  2582. }
  2583. else {
  2584. // Has to fully match.
  2585. if ((sLast - sCurrent) != (thisLast - thisCurrent) ||
  2586. !selector.regionMatches(sCurrent, name, thisCurrent,
  2587. (thisLast - thisCurrent))) {
  2588. return false;
  2589. }
  2590. }
  2591. return true;
  2592. }
  2593. if (thisDotIndex != -1) {
  2594. // Reciever references a class, just check element name.
  2595. return selector.regionMatches(sCurrent, name, thisCurrent,
  2596. thisDotIndex - thisCurrent);
  2597. }
  2598. if (thisPoundIndex != -1) {
  2599. // Reciever references an id, just check element name.
  2600. return selector.regionMatches(sCurrent, name, thisCurrent,
  2601. thisPoundIndex - thisCurrent);
  2602. }
  2603. // Fail through, no classes or ides, just check string.
  2604. return selector.regionMatches(sCurrent, name, thisCurrent,
  2605. thisLast - thisCurrent);
  2606. }
  2607. /**
  2608. * Similiar to String.indexOf, but allows an upper bound
  2609. * (this is slower in that it will still check string starting at
  2610. * start.
  2611. */
  2612. int boundedIndexOf(String string, char search, int start,
  2613. int end) {
  2614. int retValue = string.indexOf(search, start);
  2615. if (retValue >= end) {
  2616. return -1;
  2617. }
  2618. return retValue;
  2619. }
  2620. public void addAttribute(Object name, Object value) {}
  2621. public void addAttributes(AttributeSet attributes) {}
  2622. public void removeAttribute(Object name) {}
  2623. public void removeAttributes(Enumeration names) {}
  2624. public void removeAttributes(AttributeSet attributes) {}
  2625. public void setResolveParent(AttributeSet parent) {}
  2626. public String getName() {return name;}
  2627. public void addChangeListener(ChangeListener l) {}
  2628. public void removeChangeListener(ChangeListener l) {}
  2629. /** The name of the Style, which is the selector.
  2630. * This will NEVER change!
  2631. */
  2632. String name;
  2633. /** Start index of styles coming from other StyleSheets. */
  2634. private int extendedIndex;
  2635. }
  2636. // ---- Variables ---------------------------------------------
  2637. final static int DEFAULT_FONT_SIZE = 3;
  2638. private CSS css;
  2639. /** An inverted graph of the selectors. Each key will be a String tag,
  2640. * and each value will be a Hashtable. Further the key RULE is used
  2641. * for the styles attached to the particular location.
  2642. */
  2643. private Hashtable selectorMapping;
  2644. /** Maps from selector (as a string) to Style that includes all
  2645. * relevant styles. */
  2646. private Hashtable resolvedStyles;
  2647. /** Vector of StyleSheets that the rules are to reference.
  2648. */
  2649. private Vector linkedStyleSheets;
  2650. /** Where the style sheet was found. Used for relative imports. */
  2651. private URL base;
  2652. /**
  2653. * Internal StyleSheet attribute. This is used for
  2654. * caches, weights, rule storage, etc.
  2655. */
  2656. static class SheetAttribute {
  2657. SheetAttribute(String s, boolean cache) {
  2658. name = s;
  2659. this.cache = cache;
  2660. }
  2661. public String toString() {
  2662. return name;
  2663. }
  2664. public boolean isCache() {
  2665. return cache;
  2666. }
  2667. private boolean cache;
  2668. private String name;
  2669. }
  2670. static final SheetAttribute SPECIFICITY = new SheetAttribute("specificity", false);
  2671. static final SheetAttribute RULE = new SheetAttribute("rule", false);
  2672. static final SheetAttribute WEIGHT = new SheetAttribute("rule-weight", false);
  2673. static final SheetAttribute BOX_PAINTER = new SheetAttribute("box-painter", true);
  2674. static final SheetAttribute LIST_PAINTER = new SheetAttribute("list-painter", true);
  2675. static {
  2676. StyleContext.registerStaticAttributeKey(RULE);
  2677. StyleContext.registerStaticAttributeKey(WEIGHT);
  2678. StyleContext.registerStaticAttributeKey(BOX_PAINTER);
  2679. StyleContext.registerStaticAttributeKey(LIST_PAINTER);
  2680. }
  2681. /**
  2682. * Default parser for css specifications that get loaded into
  2683. * the StyleSheet.<p>
  2684. * This class is not thread safe, do not ask it to parse while it is
  2685. * in the middle of parsing.
  2686. */
  2687. class CssParser implements Serializable {
  2688. /**
  2689. * Parses the passed in CSS declaration into an AttributeSet.
  2690. */
  2691. AttributeSet parseDeclaration(String string) {
  2692. try {
  2693. return parseDeclaration(new StringReader(string));
  2694. } catch (IOException ioe) {}
  2695. return null;
  2696. }
  2697. /**
  2698. * Parses the passed in CSS declaration into an AttributeSet.
  2699. */
  2700. AttributeSet parseDeclaration(Reader r) throws IOException {
  2701. parse(base, r, true, false);
  2702. return declaration.copyAttributes();
  2703. }
  2704. /**
  2705. * Parse the given css stream
  2706. */
  2707. void parse(URL base, Reader r, boolean parseDeclaration,
  2708. boolean isLink) throws IOException {
  2709. this.base = base;
  2710. this.isLink = isLink;
  2711. declaration.removeAttributes(declaration);
  2712. selectorTokens.removeAllElements();
  2713. selectors.removeAllElements();
  2714. ruleTokens.removeAllElements();
  2715. this.parsingDeclaration = parseDeclaration;
  2716. StreamTokenizer st = new StreamTokenizer(r);
  2717. st.ordinaryChar('/');
  2718. st.slashStarComments(true);
  2719. // Treat # as a word.
  2720. st.wordChars('#', '#');
  2721. boolean inRule = parseDeclaration;
  2722. // Set to true when an '@' is encountered.
  2723. boolean inAt = false;
  2724. // Set to true if import is reached.
  2725. boolean inImport = false;
  2726. // Set to true when first selector/rule is encountered.
  2727. boolean encounteredRule = false;
  2728. // The import is added here.
  2729. StringBuffer importBuffer = null;
  2730. // Set to true if the last character was a dot ('.')
  2731. boolean lastWasDot = false;
  2732. char[] tempChar = new char[1];
  2733. for (int token = st.nextToken(); token != StreamTokenizer.TT_EOF;
  2734. token = st.nextToken()) {
  2735. // process tokens
  2736. switch (token) {
  2737. case StreamTokenizer.TT_WORD:
  2738. if (inAt) {
  2739. if (inImport) {
  2740. importBuffer.append(st.sval);
  2741. }
  2742. else if (st.sval.equals("import")) {
  2743. inImport = true;
  2744. if (importBuffer == null) {
  2745. importBuffer = new StringBuffer();
  2746. }
  2747. else {
  2748. importBuffer.setLength(0);
  2749. }
  2750. }
  2751. }
  2752. else if (inRule) {
  2753. ruleTokens.addElement(st.sval);
  2754. } else {
  2755. if (lastWasDot) {
  2756. selectorTokens.addElement('.' + st.sval);
  2757. }
  2758. else {
  2759. selectorTokens.addElement(st.sval);
  2760. }
  2761. encounteredRule = true;
  2762. }
  2763. lastWasDot = false;
  2764. break;
  2765. case StreamTokenizer.TT_NUMBER:
  2766. lastWasDot = false;
  2767. if (inAt) {
  2768. if (inImport) {
  2769. importBuffer.append(st.nval);
  2770. }
  2771. }
  2772. else if (inRule) {
  2773. ruleTokens.addElement(new Integer((int) st.nval));
  2774. } else {
  2775. if (st.nval == 0) {
  2776. // Most likely indicates a selector is starting
  2777. // with a . (eg .foo)
  2778. lastWasDot = true;
  2779. }
  2780. else {
  2781. String s = (String) selectorTokens.lastElement();
  2782. s = s + ((int) st.nval);
  2783. selectorTokens.setElementAt(s, selectorTokens.
  2784. size()-1);
  2785. encounteredRule = true;
  2786. }
  2787. }
  2788. break;
  2789. case StreamTokenizer.TT_EOL:
  2790. break;
  2791. default:
  2792. lastWasDot = false;
  2793. if (inAt) {
  2794. if (st.ttype == ';') {
  2795. inAt = false;
  2796. if (inImport) {
  2797. if (!encounteredRule) {
  2798. addImport(importBuffer.toString());
  2799. }
  2800. inImport = false;
  2801. }
  2802. }
  2803. else if (inImport) {
  2804. if (st.ttype == '\'' ||
  2805. st.ttype == '"') {
  2806. importBuffer.append(st.sval);
  2807. }
  2808. else {
  2809. importBuffer.append((char)st.ttype);
  2810. }
  2811. }
  2812. }
  2813. else {
  2814. switch(st.ttype) {
  2815. case '{':
  2816. encounteredRule = true;
  2817. addSelector();
  2818. inRule = true;
  2819. break;
  2820. case '}':
  2821. if (ruleTokens.size() > 0) {
  2822. // Won't happen if just encountered a ;
  2823. addKeyValueToDeclaration();
  2824. }
  2825. addRule();
  2826. inRule = false;
  2827. break;
  2828. case ';':
  2829. addKeyValueToDeclaration();
  2830. break;
  2831. case ':':
  2832. key = getDeclarationString();
  2833. break;
  2834. case ',':
  2835. if (inRule) {
  2836. ruleTokens.addElement(new Character(','));
  2837. } else {
  2838. addSelector();
  2839. }
  2840. break;
  2841. case '@':
  2842. inAt = true;
  2843. inImport = false;
  2844. break;
  2845. default:
  2846. if (inRule) {
  2847. ruleTokens.addElement(new Character(','));
  2848. } else {
  2849. if (selectorTokens.size() == 0) {
  2850. tempChar[0] = (char)st.ttype;
  2851. selectorTokens.addElement(new String
  2852. (tempChar));
  2853. }
  2854. else {
  2855. String s = (String) selectorTokens.
  2856. lastElement();
  2857. s = s + ((char) st.ttype);
  2858. selectorTokens.setElementAt(s,
  2859. selectorTokens.size()-1);
  2860. }
  2861. }
  2862. }
  2863. }
  2864. }
  2865. }
  2866. if (parsingDeclaration && ruleTokens.size() > 0) {
  2867. addKeyValueToDeclaration();
  2868. }
  2869. }
  2870. void addImport(String importString) {
  2871. if (importString.startsWith("url(") &&
  2872. importString.endsWith(")")) {
  2873. try {
  2874. URL url = new URL(importString.substring
  2875. (4, importString.length() - 1));
  2876. if (url != null) {
  2877. importStyleSheet(url);
  2878. }
  2879. } catch (MalformedURLException mue) {
  2880. }
  2881. }
  2882. else if (base != null) {
  2883. // Relative URL, try from base
  2884. try {
  2885. URL url = new URL(base, importString);
  2886. if (url != null) {
  2887. importStyleSheet(url);
  2888. }
  2889. }
  2890. catch (MalformedURLException muee) {
  2891. }
  2892. }
  2893. }
  2894. void addSelector() {
  2895. String[] selector = new String[selectorTokens.size()];
  2896. selectorTokens.copyInto(selector);
  2897. selectors.addElement(selector);
  2898. selectorTokens.removeAllElements();
  2899. }
  2900. void addKeyValueToDeclaration() {
  2901. CSS.Attribute cssKey = CSS.getAttribute(key);
  2902. if (cssKey != null) {
  2903. Object o = getValue(cssKey);
  2904. if (o != null) {
  2905. declaration.addAttribute(cssKey, o);
  2906. }
  2907. }
  2908. ruleTokens.removeAllElements();
  2909. }
  2910. /**
  2911. * This fetches the value for the current key using the
  2912. * current contents of the ruleTokens collection. The
  2913. * value should be converted from tokens into however
  2914. * we choose to store it in the StyleSheet.
  2915. */
  2916. Object getValue(CSS.Attribute key) {
  2917. return css.getInternalCSSValue(key, getDeclarationString());
  2918. }
  2919. String getDeclarationString() {
  2920. String s = "";
  2921. int n = ruleTokens.size();
  2922. for (int i = 0; i < n; i++) {
  2923. s += ruleTokens.elementAt(i);
  2924. }
  2925. ruleTokens.removeAllElements();
  2926. return s;
  2927. }
  2928. void addRule() {
  2929. if (parsingDeclaration) {
  2930. throw new RuntimeException("Declaration should not contain rules");
  2931. }
  2932. int n = selectors.size();
  2933. for (int i = 0; i < n; i++) {
  2934. String[] selector = (String[]) selectors.elementAt(i);
  2935. if (selector.length > 0) {
  2936. StyleSheet.this.addRule(selector, declaration, isLink);
  2937. }
  2938. }
  2939. declaration.removeAttributes(declaration);
  2940. selectors.removeAllElements();
  2941. }
  2942. Vector selectors = new Vector();
  2943. Vector selectorTokens = new Vector();
  2944. String key;
  2945. Vector ruleTokens = new Vector();
  2946. MutableAttributeSet declaration = new SimpleAttributeSet();
  2947. /** True if parsing a declaration, that is the Reader will not
  2948. * contain a selector. */
  2949. boolean parsingDeclaration;
  2950. /** True if the attributes are coming from a linked/imported style. */
  2951. boolean isLink;
  2952. URL base;
  2953. }
  2954. }