29 import java.awt.*;
30 import java.io.*;
31 import java.net.*;
32 import javax.swing.Icon;
33 import javax.swing.ImageIcon;
34 import javax.swing.UIManager;
35 import javax.swing.border.*;
36 import javax.swing.event.ChangeListener;
37 import javax.swing.text.*;
38
39 /**
40 * Support for defining the visual characteristics of
41 * HTML views being rendered. The StyleSheet is used to
42 * translate the HTML model into visual characteristics.
43 * This enables views to be customized by a look-and-feel,
44 * multiple views over the same model can be rendered
45 * differently, etc. This can be thought of as a CSS
46 * rule repository. The key for CSS attributes is an
47 * object of type CSS.Attribute. The type of the value
48 * is up to the StyleSheet implementation, but the
49 * <code>toString</code> method is required
50 * to return a string representation of CSS value.
51 * <p>
52 * The primary entry point for HTML View implementations
53 * to get their attributes is the
54 * {@link #getViewAttributes getViewAttributes}
55 * method. This should be implemented to establish the
56 * desired policy used to associate attributes with the view.
57 * Each HTMLEditorKit (i.e. and therefore each associated
58 * JEditorPane) can have its own StyleSheet, but by default one
59 * sheet will be shared by all of the HTMLEditorKit instances.
60 * HTMLDocument instance can also have a StyleSheet, which
61 * holds the document-specific CSS specifications.
62 * <p>
63 * In order for Views to store less state and therefore be
64 * more lightweight, the StyleSheet can act as a factory for
65 * painters that handle some of the rendering tasks. This allows
66 * implementations to determine what they want to cache
67 * and have the sharing potentially at the level that a
68 * selector is common to multiple views. Since the StyleSheet
69 * may be used by views over multiple documents and typically
84 *
85 * public static void main(String[] args) {
86 * HTMLEditorKit kit = new HTMLEditorKit();
87 * HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
88 * StyleSheet styles = doc.getStyleSheet();
89 *
90 * Enumeration rules = styles.getStyleNames();
91 * while (rules.hasMoreElements()) {
92 * String name = (String) rules.nextElement();
93 * Style rule = styles.getStyle(name);
94 * System.out.println(rule.toString());
95 * }
96 * System.exit(0);
97 * }
98 * }
99 *
100 * </code></pre>
101 * <p>
102 * The semantics for when a CSS style should overide visual attributes
103 * defined by an element are not well defined. For example, the html
104 * <code><body bgcolor=red></code> makes the body have a red
105 * background. But if the html file also contains the CSS rule
106 * <code>body { background: blue }</code> it becomes less clear as to
107 * what color the background of the body should be. The current
108 * implementation gives visual attributes defined in the element the
109 * highest precedence, that is they are always checked before any styles.
110 * Therefore, in the previous example the background would have a
111 * red color as the body element defines the background color to be red.
112 * <p>
113 * As already mentioned this supports CSS. We don't support the full CSS
114 * spec. Refer to the javadoc of the CSS class to see what properties
115 * we support. The two major CSS parsing related
116 * concepts we do not currently
117 * support are pseudo selectors, such as <code>A:link { color: red }</code>,
118 * and the <code>important</code> modifier.
119 *
120 * @implNote This implementation is currently
121 * incomplete. It can be replaced with alternative implementations
122 * that are complete. Future versions of this class will provide
123 * better CSS support.
124 *
125 * @author Timothy Prinzing
126 * @author Sunita Mani
127 * @author Sara Swanson
128 * @author Jill Nakata
129 */
130 @SuppressWarnings("serial") // Superclass is not serializable across versions
131 public class StyleSheet extends StyleContext {
132 // As the javadoc states, this class maintains a mapping between
133 // a CSS selector (such as p.bar) and a Style.
134 // This consists of a number of parts:
135 // . Each selector is broken down into its constituent simple selectors,
136 // and stored in an inverted graph, for example:
137 // p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt }
138 // results in the graph:
246 cacheLookup.append(attr.getAttribute(HTML.Attribute.ID));
247 }
248 else if (attr.isDefined(HTML.Attribute.CLASS)) {
249 cacheLookup.append('.');
250 cacheLookup.append(attr.getAttribute
251 (HTML.Attribute.CLASS));
252 }
253 }
254
255 Style style = getResolvedStyle(cacheLookup.toString(),
256 searchContext, t);
257 return style;
258 }
259 finally {
260 SearchBuffer.releaseSearchBuffer(sb);
261 }
262 }
263
264 /**
265 * Fetches the rule that best matches the selector given
266 * in string form. Where <code>selector</code> is a space separated
267 * String of the element names. For example, <code>selector</code>
268 * might be 'html body tr td''<p>
269 * The attributes of the returned Style will change
270 * as rules are added and removed. That is if you to ask for a rule
271 * with a selector "table p" and a new rule was added with a selector
272 * of "p" the returned Style would include the new attributes from
273 * the rule "p".
274 *
275 * @param selector a space separated String of the element names.
276 * @return the rule that best matches the selector.
277 */
278 public Style getRule(String selector) {
279 selector = cleanSelectorString(selector);
280 if (selector != null) {
281 Style style = getResolvedStyle(selector);
282 return style;
283 }
284 return null;
285 }
286
287 /**
379 mapping = mapping.getChildSelectorMapping(selectors[i],
380 true);
381 }
382 Style rule = mapping.getStyle();
383 if (rule != null) {
384 mapping.setStyle(null);
385 if (resolvedStyles.size() > 0) {
386 Enumeration<ResolvedStyle> values = resolvedStyles.elements();
387 while (values.hasMoreElements()) {
388 ResolvedStyle style = values.nextElement();
389 style.removeStyle(rule);
390 }
391 }
392 }
393 }
394 }
395 super.removeStyle(nm);
396 }
397
398 /**
399 * Adds the rules from the StyleSheet <code>ss</code> to those of
400 * the receiver. <code>ss's</code> rules will override the rules of
401 * any previously added style sheets. An added StyleSheet will never
402 * override the rules of the receiving style sheet.
403 *
404 * @param ss a StyleSheet
405 * @since 1.3
406 */
407 public void addStyleSheet(StyleSheet ss) {
408 synchronized(this) {
409 if (linkedStyleSheets == null) {
410 linkedStyleSheets = new Vector<StyleSheet>();
411 }
412 if (!linkedStyleSheets.contains(ss)) {
413 int index = 0;
414 if (ss instanceof javax.swing.plaf.UIResource
415 && linkedStyleSheets.size() > 1) {
416 index = linkedStyleSheets.size() - 1;
417 }
418 linkedStyleSheets.insertElementAt(ss, index);
419 linkStyleSheetAt(ss, index);
420 }
421 }
422 }
423
424 /**
425 * Removes the StyleSheet <code>ss</code> from those of the receiver.
426 *
427 * @param ss a StyleSheet
428 * @since 1.3
429 */
430 public void removeStyleSheet(StyleSheet ss) {
431 synchronized(this) {
432 if (linkedStyleSheets != null) {
433 int index = linkedStyleSheets.indexOf(ss);
434 if (index != -1) {
435 linkedStyleSheets.removeElementAt(index);
436 unlinkStyleSheet(ss, index);
437 if (index == 0 && linkedStyleSheets.size() == 0) {
438 linkedStyleSheets = null;
439 }
440 }
441 }
442 }
443 }
444
445 //
452 *
453 * @return an array of StyleSheets.
454 * @since 1.3
455 */
456 public StyleSheet[] getStyleSheets() {
457 StyleSheet[] retValue;
458
459 synchronized(this) {
460 if (linkedStyleSheets != null) {
461 retValue = new StyleSheet[linkedStyleSheets.size()];
462 linkedStyleSheets.copyInto(retValue);
463 }
464 else {
465 retValue = null;
466 }
467 }
468 return retValue;
469 }
470
471 /**
472 * Imports a style sheet from <code>url</code>. The resulting rules
473 * are directly added to the receiver. If you do not want the rules
474 * to become part of the receiver, create a new StyleSheet and use
475 * addStyleSheet to link it in.
476 *
477 * @param url an url
478 * @since 1.3
479 */
480 public void importStyleSheet(URL url) {
481 try {
482 InputStream is;
483
484 is = url.openStream();
485 Reader r = new BufferedReader(new InputStreamReader(is));
486 CssParser parser = new CssParser();
487 parser.parse(url, r, false, true);
488 r.close();
489 is.close();
490 } catch (Throwable e) {
491 // on error we simply have no styles... the html
492 // will look mighty wrong but still function.
493 }
494 }
495
496 /**
497 * Sets the base. All import statements that are relative, will be
498 * relative to <code>base</code>.
499 *
500 * @param base a base.
501 * @since 1.3
502 */
503 public void setBase(URL base) {
504 this.base = base;
505 }
506
507 /**
508 * Returns the base.
509 *
510 * @return the base.
511 * @since 1.3
512 */
513 public URL getBase() {
514 return base;
515 }
516
517 /**
518 * Adds a CSS attribute to the given set.
1014 */
1015 public float getPointSize(String size) {
1016 return css.getPointSize(size, this);
1017 }
1018
1019 /**
1020 * Converts a color string such as "RED" or "#NNNNNN" to a Color.
1021 * Note: This will only convert the HTML3.2 color strings
1022 * or a string of length 7;
1023 * otherwise, it will return null.
1024 *
1025 * @param string color string such as "RED" or "#NNNNNN"
1026 * @return the color
1027 */
1028 public Color stringToColor(String string) {
1029 return CSS.stringToColor(string);
1030 }
1031
1032 /**
1033 * Returns the ImageIcon to draw in the background for
1034 * <code>attr</code>.
1035 */
1036 ImageIcon getBackgroundImage(AttributeSet attr) {
1037 Object value = attr.getAttribute(CSS.Attribute.BACKGROUND_IMAGE);
1038
1039 if (value != null) {
1040 return ((CSS.BackgroundImage)value).getImage(getBase());
1041 }
1042 return null;
1043 }
1044
1045 /**
1046 * Adds a rule into the StyleSheet.
1047 *
1048 * @param selector the selector to use for the rule.
1049 * This will be a set of simple selectors, and must
1050 * be a length of 1 or greater.
1051 * @param declaration the set of CSS attributes that
1052 * make up the rule.
1053 */
1054 void addRule(String[] selector, AttributeSet declaration,
1080 rule = altRule;
1081 mapping.setStyle(rule);
1082 refreshResolvedRules(selectorName, selector, rule,
1083 mapping.getSpecificity());
1084 }
1085 }
1086 }
1087 if (isLinked) {
1088 rule = getLinkedStyle(rule);
1089 }
1090 rule.addAttributes(declaration);
1091 }
1092
1093 //
1094 // The following gaggle of methods is used in maintaining the rules from
1095 // the sheet.
1096 //
1097
1098 /**
1099 * Updates the attributes of the rules to reference any related
1100 * rules in <code>ss</code>.
1101 */
1102 private synchronized void linkStyleSheetAt(StyleSheet ss, int index) {
1103 if (resolvedStyles.size() > 0) {
1104 Enumeration<ResolvedStyle> values = resolvedStyles.elements();
1105 while (values.hasMoreElements()) {
1106 ResolvedStyle rule = values.nextElement();
1107 rule.insertExtendedStyleAt(ss.getRule(rule.getName()),
1108 index);
1109 }
1110 }
1111 }
1112
1113 /**
1114 * Removes references to the rules in <code>ss</code>.
1115 * <code>index</code> gives the index the StyleSheet was at, that is
1116 * how many StyleSheets had been added before it.
1117 */
1118 private synchronized void unlinkStyleSheet(StyleSheet ss, int index) {
1119 if (resolvedStyles.size() > 0) {
1120 Enumeration<ResolvedStyle> values = resolvedStyles.elements();
1121 while (values.hasMoreElements()) {
1122 ResolvedStyle rule = values.nextElement();
1123 rule.removeExtendedStyleAt(index);
1124 }
1125 }
1126 }
1127
1128 /**
1129 * Returns the simple selectors that comprise selector.
1130 */
1131 /* protected */
1132 String[] getSimpleSelectors(String selector) {
1133 selector = cleanSelectorString(selector);
1134 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1135 @SuppressWarnings("unchecked")
1290 /**
1291 * Returns the style that linked attributes should be added to. This
1292 * will create the style if necessary.
1293 */
1294 private Style getLinkedStyle(Style localStyle) {
1295 // NOTE: This is not synchronized, and the caller of this does
1296 // not synchronize. There is the chance for one of the callers to
1297 // overwrite the existing resolved parent, but it is quite rare.
1298 // The reason this is left like this is because setResolveParent
1299 // will fire a ChangeEvent. It is really, REALLY bad for us to
1300 // hold a lock when calling outside of us, it may cause a deadlock.
1301 Style retStyle = (Style)localStyle.getResolveParent();
1302 if (retStyle == null) {
1303 retStyle = addStyle(null, null);
1304 localStyle.setResolveParent(retStyle);
1305 }
1306 return retStyle;
1307 }
1308
1309 /**
1310 * Returns the resolved style for <code>selector</code>. This will
1311 * create the resolved style, if necessary.
1312 */
1313 private synchronized Style getResolvedStyle(String selector,
1314 Vector<Element> elements,
1315 HTML.Tag t) {
1316 Style retStyle = resolvedStyles.get(selector);
1317 if (retStyle == null) {
1318 retStyle = createResolvedStyle(selector, elements, t);
1319 }
1320 return retStyle;
1321 }
1322
1323 /**
1324 * Returns the resolved style for <code>selector</code>. This will
1325 * create the resolved style, if necessary.
1326 */
1327 private synchronized Style getResolvedStyle(String selector) {
1328 Style retStyle = resolvedStyles.get(selector);
1329 if (retStyle == null) {
1330 retStyle = createResolvedStyle(selector);
1331 }
1332 return retStyle;
1333 }
1334
1335 /**
1336 * Adds <code>mapping</code> to <code>elements</code>. It is added
1337 * such that <code>elements</code> will remain ordered by
1338 * specificity.
1339 */
1340 private void addSortedStyle(SelectorMapping mapping, Vector<SelectorMapping> elements) {
1341 int size = elements.size();
1342
1343 if (size > 0) {
1344 int specificity = mapping.getSpecificity();
1345
1346 for (int counter = 0; counter < size; counter++) {
1347 if (specificity >= elements.elementAt(counter).getSpecificity()) {
1348 elements.insertElementAt(mapping, counter);
1349 return;
1350 }
1351 }
1352 }
1353 elements.addElement(mapping);
1354 }
1355
1356 /**
1357 * Adds <code>parentMapping</code> to <code>styles</code>, and
1358 * recursively calls this method if <code>parentMapping</code> has
1359 * any child mappings for any of the Elements in <code>elements</code>.
1360 */
1361 private synchronized void getStyles(SelectorMapping parentMapping,
1362 Vector<SelectorMapping> styles,
1363 String[] tags, String[] ids, String[] classes,
1364 int index, int numElements,
1365 Hashtable<SelectorMapping, SelectorMapping> alreadyChecked) {
1366 // Avoid desending the same mapping twice.
1367 if (alreadyChecked.contains(parentMapping)) {
1368 return;
1369 }
1370 alreadyChecked.put(parentMapping, parentMapping);
1371 Style style = parentMapping.getStyle();
1372 if (style != null) {
1373 addSortedStyle(parentMapping, styles);
1374 }
1375 for (int counter = index; counter < numElements; counter++) {
1376 String tagString = tags[counter];
1377 if (tagString != null) {
1378 SelectorMapping childMapping = parentMapping.
1379 getChildSelectorMapping(tagString, false);
1400 String idName = ids[counter];
1401 childMapping = parentMapping.getChildSelectorMapping(
1402 tagString + "#" + idName, false);
1403 if (childMapping != null) {
1404 getStyles(childMapping, styles, tags, ids, classes,
1405 counter + 1, numElements, alreadyChecked);
1406 }
1407 childMapping = parentMapping.getChildSelectorMapping(
1408 "#" + idName, false);
1409 if (childMapping != null) {
1410 getStyles(childMapping, styles, tags, ids, classes,
1411 counter + 1, numElements, alreadyChecked);
1412 }
1413 }
1414 }
1415 }
1416 }
1417
1418 /**
1419 * Creates and returns a Style containing all the rules that match
1420 * <code>selector</code>.
1421 */
1422 private synchronized Style createResolvedStyle(String selector,
1423 String[] tags,
1424 String[] ids, String[] classes) {
1425 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1426 @SuppressWarnings("unchecked")
1427 Vector<SelectorMapping> tempVector = sb.getVector();
1428 @SuppressWarnings("unchecked")
1429 Hashtable<SelectorMapping, SelectorMapping> tempHashtable = sb.getHashtable();
1430 // Determine all the Styles that are appropriate, placing them
1431 // in tempVector
1432 try {
1433 SelectorMapping mapping = getRootSelectorMapping();
1434 int numElements = tags.length;
1435 String tagString = tags[0];
1436 SelectorMapping childMapping = mapping.getChildSelectorMapping(
1437 tagString, false);
1438 if (childMapping != null) {
1439 getStyles(childMapping, tempVector, tags, ids, classes, 1,
1440 numElements, tempHashtable);
1483 AttributeSet attr = linkedStyleSheets.elementAt(counter).getRule(selector);
1484 if (attr == null) {
1485 attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
1486 }
1487 else {
1488 attrs[counter + numStyles] = attr;
1489 }
1490 }
1491 ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
1492 numStyles);
1493 resolvedStyles.put(selector, retStyle);
1494 return retStyle;
1495 }
1496 finally {
1497 SearchBuffer.releaseSearchBuffer(sb);
1498 }
1499 }
1500
1501 /**
1502 * Creates and returns a Style containing all the rules that
1503 * matches <code>selector</code>.
1504 *
1505 * @param elements a Vector of all the Elements
1506 * the style is being asked for. The
1507 * first Element is the deepest Element, with the last Element
1508 * representing the root.
1509 * @param t the Tag to use for
1510 * the first Element in <code>elements</code>
1511 */
1512 private Style createResolvedStyle(String selector, Vector<Element> elements,
1513 HTML.Tag t) {
1514 int numElements = elements.size();
1515 // Build three arrays, one for tags, one for class's, and one for
1516 // id's
1517 String tags[] = new String[numElements];
1518 String ids[] = new String[numElements];
1519 String classes[] = new String[numElements];
1520 for (int counter = 0; counter < numElements; counter++) {
1521 Element e = elements.elementAt(counter);
1522 AttributeSet attr = e.getAttributes();
1523 if (counter == 0 && e.isLeaf()) {
1524 // For leafs, we use the second tier attributes.
1525 Object testAttr = attr.getAttribute(t);
1526 if (testAttr instanceof AttributeSet) {
1527 attr = (AttributeSet)testAttr;
1528 }
1529 else {
1530 attr = null;
1547 classes[counter] = null;
1548 }
1549 if (attr.isDefined(HTML.Attribute.ID)) {
1550 ids[counter] = attr.getAttribute(HTML.Attribute.ID).
1551 toString();
1552 }
1553 else {
1554 ids[counter] = null;
1555 }
1556 }
1557 else {
1558 tags[counter] = ids[counter] = classes[counter] = null;
1559 }
1560 }
1561 tags[0] = t.toString();
1562 return createResolvedStyle(selector, tags, ids, classes);
1563 }
1564
1565 /**
1566 * Creates and returns a Style containing all the rules that match
1567 * <code>selector</code>. It is assumed that each simple selector
1568 * in <code>selector</code> is separated by a space.
1569 */
1570 private Style createResolvedStyle(String selector) {
1571 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1572 // Will contain the tags, ids, and classes, in that order.
1573 @SuppressWarnings("unchecked")
1574 Vector<String> elements = sb.getVector();
1575 try {
1576 boolean done;
1577 int dotIndex = 0;
1578 int spaceIndex;
1579 int poundIndex = 0;
1580 int lastIndex = 0;
1581 int length = selector.length();
1582 while (lastIndex < length) {
1583 if (dotIndex == lastIndex) {
1584 dotIndex = selector.indexOf('.', lastIndex);
1585 }
1586 if (poundIndex == lastIndex) {
1587 poundIndex = selector.indexOf('#', lastIndex);
1588 }
1748
1749 /**
1750 * Returns an instance of SearchBuffer. Be sure and issue
1751 * a releaseSearchBuffer when done with it.
1752 */
1753 static SearchBuffer obtainSearchBuffer() {
1754 SearchBuffer sb;
1755 try {
1756 if(!searchBuffers.empty()) {
1757 sb = searchBuffers.pop();
1758 } else {
1759 sb = new SearchBuffer();
1760 }
1761 } catch (EmptyStackException ese) {
1762 sb = new SearchBuffer();
1763 }
1764 return sb;
1765 }
1766
1767 /**
1768 * Adds <code>sb</code> to the stack of SearchBuffers that can
1769 * be used.
1770 */
1771 static void releaseSearchBuffer(SearchBuffer sb) {
1772 sb.empty();
1773 searchBuffers.push(sb);
1774 }
1775
1776 StringBuffer getStringBuffer() {
1777 if (stringBuffer == null) {
1778 stringBuffer = new StringBuffer();
1779 }
1780 return stringBuffer;
1781 }
1782
1783 Vector getVector() {
1784 if (vector == null) {
1785 vector = new Vector();
1786 }
1787 return vector;
1788 }
2140
2141 if (childtype == null) {
2142 if (type == null) {
2143 // Parent view.
2144 View v = childView.getParent();
2145 HTMLDocument doc = (HTMLDocument)v.getDocument();
2146 if (HTMLDocument.matchNameAttribute(v.getElement().getAttributes(),
2147 HTML.Tag.OL)) {
2148 childtype = CSS.Value.DECIMAL;
2149 } else {
2150 childtype = CSS.Value.DISC;
2151 }
2152 } else {
2153 childtype = type;
2154 }
2155 }
2156 return childtype;
2157 }
2158
2159 /**
2160 * Obtains the starting index from <code>parent</code>.
2161 */
2162 private void getStart(View parent) {
2163 checkedForStart = true;
2164 Element element = parent.getElement();
2165 if (element != null) {
2166 AttributeSet attr = element.getAttributes();
2167 Object startValue;
2168 if (attr != null && attr.isDefined(HTML.Attribute.START) &&
2169 (startValue = attr.getAttribute
2170 (HTML.Attribute.START)) != null &&
2171 (startValue instanceof String)) {
2172
2173 try {
2174 start = Integer.parseInt((String)startValue);
2175 }
2176 catch (NumberFormatException nfe) {}
2177 }
2178 }
2179 }
2180
2181 /**
2182 * Returns an integer that should be used to render the child at
2183 * <code>childIndex</code> with. The retValue will usually be
2184 * <code>childIndex</code> + 1, unless <code>parentView</code>
2185 * has some Views that do not represent LI's, or one of the views
2186 * has a HTML.Attribute.START specified.
2187 */
2188 private int getRenderIndex(View parentView, int childIndex) {
2189 if (!checkedForStart) {
2190 getStart(parentView);
2191 }
2192 int retIndex = childIndex;
2193 for (int counter = childIndex; counter >= 0; counter--) {
2194 AttributeSet as = parentView.getElement().getElement(counter).
2195 getAttributes();
2196 if (as.getAttribute(StyleConstants.NameAttribute) !=
2197 HTML.Tag.LI) {
2198 retIndex--;
2199 } else if (as.isDefined(HTML.Attribute.VALUE)) {
2200 Object value = as.getAttribute(HTML.Attribute.VALUE);
2201 if (value != null &&
2202 (value instanceof String)) {
2203 try {
2204 int iValue = Integer.parseInt((String)value);
2843 * A subclass of MuxingAttributeSet that implements Style. Currently
2844 * the MutableAttributeSet methods are unimplemented, that is they
2845 * do nothing.
2846 */
2847 // PENDING(sky): Decide what to do with this. Either make it
2848 // contain a SimpleAttributeSet that modify methods are delegated to,
2849 // or change getRule to return an AttributeSet and then don't make this
2850 // implement Style.
2851 @SuppressWarnings("serial") // Same-version serialization only
2852 static class ResolvedStyle extends MuxingAttributeSet implements
2853 Serializable, Style {
2854 ResolvedStyle(String name, AttributeSet[] attrs, int extendedIndex) {
2855 super(attrs);
2856 this.name = name;
2857 this.extendedIndex = extendedIndex;
2858 }
2859
2860 /**
2861 * Inserts a Style into the receiver so that the styles the
2862 * receiver represents are still ordered by specificity.
2863 * <code>style</code> will be added before any extended styles, that
2864 * is before extendedIndex.
2865 */
2866 synchronized void insertStyle(Style style, int specificity) {
2867 AttributeSet[] attrs = getAttributes();
2868 int maxCounter = attrs.length;
2869 int counter = 0;
2870 for (;counter < extendedIndex; counter++) {
2871 if (specificity > getSpecificity(((Style)attrs[counter]).
2872 getName())) {
2873 break;
2874 }
2875 }
2876 insertAttributeSetAt(style, counter);
2877 extendedIndex++;
2878 }
2879
2880 /**
2881 * Removes a previously added style. This will do nothing if
2882 * <code>style</code> is not referenced by the receiver.
2883 */
2884 synchronized void removeStyle(Style style) {
2885 AttributeSet[] attrs = getAttributes();
2886
2887 for (int counter = attrs.length - 1; counter >= 0; counter--) {
2888 if (attrs[counter] == style) {
2889 removeAttributeSetAt(counter);
2890 if (counter < extendedIndex) {
2891 extendedIndex--;
2892 }
2893 break;
2894 }
2895 }
2896 }
2897
2898 /**
2899 * Adds <code>s</code> as one of the Attributesets to look up
2900 * attributes in.
2901 */
2902 synchronized void insertExtendedStyleAt(Style attr, int index) {
2903 insertAttributeSetAt(attr, extendedIndex + index);
2904 }
2905
2906 /**
2907 * Adds <code>s</code> as one of the AttributeSets to look up
2908 * attributes in. It will be the AttributeSet last checked.
2909 */
2910 synchronized void addExtendedStyle(Style attr) {
2911 insertAttributeSetAt(attr, getAttributes().length);
2912 }
2913
2914 /**
2915 * Removes the style at <code>index</code> +
2916 * <code>extendedIndex</code>.
2917 */
2918 synchronized void removeExtendedStyleAt(int index) {
2919 removeAttributeSetAt(extendedIndex + index);
2920 }
2921
2922 /**
2923 * Returns true if the receiver matches <code>selector</code>, where
2924 * a match is defined by the CSS rule matching.
2925 * Each simple selector must be separated by a single space.
2926 */
2927 protected boolean matches(String selector) {
2928 int sLast = selector.length();
2929
2930 if (sLast == 0) {
2931 return false;
2932 }
2933 int thisLast = name.length();
2934 int sCurrent = selector.lastIndexOf(' ');
2935 int thisCurrent = name.lastIndexOf(' ');
2936 if (sCurrent >= 0) {
2937 sCurrent++;
2938 }
2939 if (thisCurrent >= 0) {
2940 thisCurrent++;
2941 }
2942 if (!matches(selector, sCurrent, sLast, thisCurrent, thisLast)) {
2943 return false;
3067 public void removeAttributes(AttributeSet attributes) {}
3068 public void setResolveParent(AttributeSet parent) {}
3069 public String getName() {return name;}
3070 public void addChangeListener(ChangeListener l) {}
3071 public void removeChangeListener(ChangeListener l) {}
3072 public ChangeListener[] getChangeListeners() {
3073 return new ChangeListener[0];
3074 }
3075
3076 /** The name of the Style, which is the selector.
3077 * This will NEVER change!
3078 */
3079 String name;
3080 /** Start index of styles coming from other StyleSheets. */
3081 private int extendedIndex;
3082 }
3083
3084
3085 /**
3086 * SelectorMapping contains a specifitiy, as an integer, and an associated
3087 * Style. It can also reference children <code>SelectorMapping</code>s,
3088 * so that it behaves like a tree.
3089 * <p>
3090 * This is not thread safe, it is assumed the caller will take the
3091 * necessary precations if this is to be used in a threaded environment.
3092 */
3093 @SuppressWarnings("serial") // Same-version serialization only
3094 static class SelectorMapping implements Serializable {
3095 public SelectorMapping(int specificity) {
3096 this.specificity = specificity;
3097 }
3098
3099 /**
3100 * Returns the specificity this mapping represents.
3101 */
3102 public int getSpecificity() {
3103 return specificity;
3104 }
3105
3106 /**
3107 * Sets the Style associated with this mapping.
3108 */
3109 public void setStyle(Style style) {
3110 this.style = style;
3111 }
3112
3113 /**
3114 * Returns the Style associated with this mapping.
3115 */
3116 public Style getStyle() {
3117 return style;
3118 }
3119
3120 /**
3121 * Returns the child mapping identified by the simple selector
3122 * <code>selector</code>. If a child mapping does not exist for
3123 *<code>selector</code>, and <code>create</code> is true, a new
3124 * one will be created.
3125 */
3126 public SelectorMapping getChildSelectorMapping(String selector,
3127 boolean create) {
3128 SelectorMapping retValue = null;
3129
3130 if (children != null) {
3131 retValue = children.get(selector);
3132 }
3133 else if (create) {
3134 children = new HashMap<String, SelectorMapping>(7);
3135 }
3136 if (retValue == null && create) {
3137 int specificity = getChildSpecificity(selector);
3138
3139 retValue = createChildSelectorMapping(specificity);
3140 children.put(selector, retValue);
3141 }
3142 return retValue;
3143 }
3144
3145 /**
3146 * Creates a child <code>SelectorMapping</code> with the specified
3147 * <code>specificity</code>.
3148 */
3149 protected SelectorMapping createChildSelectorMapping(int specificity) {
3150 return new SelectorMapping(specificity);
3151 }
3152
3153 /**
3154 * Returns the specificity for the child selector
3155 * <code>selector</code>.
3156 */
3157 protected int getChildSpecificity(String selector) {
3158 // class (.) 100
3159 // id (#) 10000
3160 char firstChar = selector.charAt(0);
3161 int specificity = getSpecificity();
3162
3163 if (firstChar == '.') {
3164 specificity += 100;
3165 }
3166 else if (firstChar == '#') {
3167 specificity += 10000;
3168 }
3169 else {
3170 specificity += 1;
3171 if (selector.indexOf('.') != -1) {
3172 specificity += 100;
3173 }
3174 if (selector.indexOf('#') != -1) {
3175 specificity += 10000;
3247 * Parse the given CSS stream
3248 */
3249 public void parse(URL base, Reader r, boolean parseDeclaration,
3250 boolean isLink) throws IOException {
3251 this.base = base;
3252 this.isLink = isLink;
3253 this.parsingDeclaration = parseDeclaration;
3254 declaration.removeAttributes(declaration);
3255 selectorTokens.removeAllElements();
3256 selectors.removeAllElements();
3257 propertyName = null;
3258 parser.parse(r, this, parseDeclaration);
3259 }
3260
3261 //
3262 // CSSParserCallback methods, public to implement the interface.
3263 //
3264
3265 /**
3266 * Invoked when a valid @import is encountered, will call
3267 * <code>importStyleSheet</code> if a
3268 * <code>MalformedURLException</code> is not thrown in creating
3269 * the URL.
3270 */
3271 public void handleImport(String importString) {
3272 URL url = CSS.getURL(base, importString);
3273 if (url != null) {
3274 importStyleSheet(url);
3275 }
3276 }
3277
3278 /**
3279 * A selector has been encountered.
3280 */
3281 public void handleSelector(String selector) {
3282 //class and index selectors are case sensitive
3283 if (!(selector.startsWith(".")
3284 || selector.startsWith("#"))) {
3285 selector = selector.toLowerCase();
3286 }
3287 int length = selector.length();
3288
|
29 import java.awt.*;
30 import java.io.*;
31 import java.net.*;
32 import javax.swing.Icon;
33 import javax.swing.ImageIcon;
34 import javax.swing.UIManager;
35 import javax.swing.border.*;
36 import javax.swing.event.ChangeListener;
37 import javax.swing.text.*;
38
39 /**
40 * Support for defining the visual characteristics of
41 * HTML views being rendered. The StyleSheet is used to
42 * translate the HTML model into visual characteristics.
43 * This enables views to be customized by a look-and-feel,
44 * multiple views over the same model can be rendered
45 * differently, etc. This can be thought of as a CSS
46 * rule repository. The key for CSS attributes is an
47 * object of type CSS.Attribute. The type of the value
48 * is up to the StyleSheet implementation, but the
49 * {@code toString} method is required
50 * to return a string representation of CSS value.
51 * <p>
52 * The primary entry point for HTML View implementations
53 * to get their attributes is the
54 * {@link #getViewAttributes getViewAttributes}
55 * method. This should be implemented to establish the
56 * desired policy used to associate attributes with the view.
57 * Each HTMLEditorKit (i.e. and therefore each associated
58 * JEditorPane) can have its own StyleSheet, but by default one
59 * sheet will be shared by all of the HTMLEditorKit instances.
60 * HTMLDocument instance can also have a StyleSheet, which
61 * holds the document-specific CSS specifications.
62 * <p>
63 * In order for Views to store less state and therefore be
64 * more lightweight, the StyleSheet can act as a factory for
65 * painters that handle some of the rendering tasks. This allows
66 * implementations to determine what they want to cache
67 * and have the sharing potentially at the level that a
68 * selector is common to multiple views. Since the StyleSheet
69 * may be used by views over multiple documents and typically
84 *
85 * public static void main(String[] args) {
86 * HTMLEditorKit kit = new HTMLEditorKit();
87 * HTMLDocument doc = (HTMLDocument) kit.createDefaultDocument();
88 * StyleSheet styles = doc.getStyleSheet();
89 *
90 * Enumeration rules = styles.getStyleNames();
91 * while (rules.hasMoreElements()) {
92 * String name = (String) rules.nextElement();
93 * Style rule = styles.getStyle(name);
94 * System.out.println(rule.toString());
95 * }
96 * System.exit(0);
97 * }
98 * }
99 *
100 * </code></pre>
101 * <p>
102 * The semantics for when a CSS style should overide visual attributes
103 * defined by an element are not well defined. For example, the html
104 * {@code <body bgcolor=red>} makes the body have a red
105 * background. But if the html file also contains the CSS rule
106 * {@code body { background: blue }} it becomes less clear as to
107 * what color the background of the body should be. The current
108 * implementation gives visual attributes defined in the element the
109 * highest precedence, that is they are always checked before any styles.
110 * Therefore, in the previous example the background would have a
111 * red color as the body element defines the background color to be red.
112 * <p>
113 * As already mentioned this supports CSS. We don't support the full CSS
114 * spec. Refer to the javadoc of the CSS class to see what properties
115 * we support. The two major CSS parsing related
116 * concepts we do not currently
117 * support are pseudo selectors, such as {@code A:link { color: red }},
118 * and the {@code important} modifier.
119 *
120 * @implNote This implementation is currently
121 * incomplete. It can be replaced with alternative implementations
122 * that are complete. Future versions of this class will provide
123 * better CSS support.
124 *
125 * @author Timothy Prinzing
126 * @author Sunita Mani
127 * @author Sara Swanson
128 * @author Jill Nakata
129 */
130 @SuppressWarnings("serial") // Superclass is not serializable across versions
131 public class StyleSheet extends StyleContext {
132 // As the javadoc states, this class maintains a mapping between
133 // a CSS selector (such as p.bar) and a Style.
134 // This consists of a number of parts:
135 // . Each selector is broken down into its constituent simple selectors,
136 // and stored in an inverted graph, for example:
137 // p { color: red } ol p { font-size: 10pt } ul p { font-size: 12pt }
138 // results in the graph:
246 cacheLookup.append(attr.getAttribute(HTML.Attribute.ID));
247 }
248 else if (attr.isDefined(HTML.Attribute.CLASS)) {
249 cacheLookup.append('.');
250 cacheLookup.append(attr.getAttribute
251 (HTML.Attribute.CLASS));
252 }
253 }
254
255 Style style = getResolvedStyle(cacheLookup.toString(),
256 searchContext, t);
257 return style;
258 }
259 finally {
260 SearchBuffer.releaseSearchBuffer(sb);
261 }
262 }
263
264 /**
265 * Fetches the rule that best matches the selector given
266 * in string form. Where {@code selector} is a space separated
267 * String of the element names. For example, {@code selector}
268 * might be 'html body tr td''<p>
269 * The attributes of the returned Style will change
270 * as rules are added and removed. That is if you to ask for a rule
271 * with a selector "table p" and a new rule was added with a selector
272 * of "p" the returned Style would include the new attributes from
273 * the rule "p".
274 *
275 * @param selector a space separated String of the element names.
276 * @return the rule that best matches the selector.
277 */
278 public Style getRule(String selector) {
279 selector = cleanSelectorString(selector);
280 if (selector != null) {
281 Style style = getResolvedStyle(selector);
282 return style;
283 }
284 return null;
285 }
286
287 /**
379 mapping = mapping.getChildSelectorMapping(selectors[i],
380 true);
381 }
382 Style rule = mapping.getStyle();
383 if (rule != null) {
384 mapping.setStyle(null);
385 if (resolvedStyles.size() > 0) {
386 Enumeration<ResolvedStyle> values = resolvedStyles.elements();
387 while (values.hasMoreElements()) {
388 ResolvedStyle style = values.nextElement();
389 style.removeStyle(rule);
390 }
391 }
392 }
393 }
394 }
395 super.removeStyle(nm);
396 }
397
398 /**
399 * Adds the rules from the StyleSheet {@code ss} to those of
400 * the receiver. {@code ss's} rules will override the rules of
401 * any previously added style sheets. An added StyleSheet will never
402 * override the rules of the receiving style sheet.
403 *
404 * @param ss a StyleSheet
405 * @since 1.3
406 */
407 public void addStyleSheet(StyleSheet ss) {
408 synchronized(this) {
409 if (linkedStyleSheets == null) {
410 linkedStyleSheets = new Vector<StyleSheet>();
411 }
412 if (!linkedStyleSheets.contains(ss)) {
413 int index = 0;
414 if (ss instanceof javax.swing.plaf.UIResource
415 && linkedStyleSheets.size() > 1) {
416 index = linkedStyleSheets.size() - 1;
417 }
418 linkedStyleSheets.insertElementAt(ss, index);
419 linkStyleSheetAt(ss, index);
420 }
421 }
422 }
423
424 /**
425 * Removes the StyleSheet {@code ss} from those of the receiver.
426 *
427 * @param ss a StyleSheet
428 * @since 1.3
429 */
430 public void removeStyleSheet(StyleSheet ss) {
431 synchronized(this) {
432 if (linkedStyleSheets != null) {
433 int index = linkedStyleSheets.indexOf(ss);
434 if (index != -1) {
435 linkedStyleSheets.removeElementAt(index);
436 unlinkStyleSheet(ss, index);
437 if (index == 0 && linkedStyleSheets.size() == 0) {
438 linkedStyleSheets = null;
439 }
440 }
441 }
442 }
443 }
444
445 //
452 *
453 * @return an array of StyleSheets.
454 * @since 1.3
455 */
456 public StyleSheet[] getStyleSheets() {
457 StyleSheet[] retValue;
458
459 synchronized(this) {
460 if (linkedStyleSheets != null) {
461 retValue = new StyleSheet[linkedStyleSheets.size()];
462 linkedStyleSheets.copyInto(retValue);
463 }
464 else {
465 retValue = null;
466 }
467 }
468 return retValue;
469 }
470
471 /**
472 * Imports a style sheet from {@code url}. The resulting rules
473 * are directly added to the receiver. If you do not want the rules
474 * to become part of the receiver, create a new StyleSheet and use
475 * addStyleSheet to link it in.
476 *
477 * @param url an url
478 * @since 1.3
479 */
480 public void importStyleSheet(URL url) {
481 try {
482 InputStream is;
483
484 is = url.openStream();
485 Reader r = new BufferedReader(new InputStreamReader(is));
486 CssParser parser = new CssParser();
487 parser.parse(url, r, false, true);
488 r.close();
489 is.close();
490 } catch (Throwable e) {
491 // on error we simply have no styles... the html
492 // will look mighty wrong but still function.
493 }
494 }
495
496 /**
497 * Sets the base. All import statements that are relative, will be
498 * relative to {@code base}.
499 *
500 * @param base a base.
501 * @since 1.3
502 */
503 public void setBase(URL base) {
504 this.base = base;
505 }
506
507 /**
508 * Returns the base.
509 *
510 * @return the base.
511 * @since 1.3
512 */
513 public URL getBase() {
514 return base;
515 }
516
517 /**
518 * Adds a CSS attribute to the given set.
1014 */
1015 public float getPointSize(String size) {
1016 return css.getPointSize(size, this);
1017 }
1018
1019 /**
1020 * Converts a color string such as "RED" or "#NNNNNN" to a Color.
1021 * Note: This will only convert the HTML3.2 color strings
1022 * or a string of length 7;
1023 * otherwise, it will return null.
1024 *
1025 * @param string color string such as "RED" or "#NNNNNN"
1026 * @return the color
1027 */
1028 public Color stringToColor(String string) {
1029 return CSS.stringToColor(string);
1030 }
1031
1032 /**
1033 * Returns the ImageIcon to draw in the background for
1034 * {@code attr}.
1035 */
1036 ImageIcon getBackgroundImage(AttributeSet attr) {
1037 Object value = attr.getAttribute(CSS.Attribute.BACKGROUND_IMAGE);
1038
1039 if (value != null) {
1040 return ((CSS.BackgroundImage)value).getImage(getBase());
1041 }
1042 return null;
1043 }
1044
1045 /**
1046 * Adds a rule into the StyleSheet.
1047 *
1048 * @param selector the selector to use for the rule.
1049 * This will be a set of simple selectors, and must
1050 * be a length of 1 or greater.
1051 * @param declaration the set of CSS attributes that
1052 * make up the rule.
1053 */
1054 void addRule(String[] selector, AttributeSet declaration,
1080 rule = altRule;
1081 mapping.setStyle(rule);
1082 refreshResolvedRules(selectorName, selector, rule,
1083 mapping.getSpecificity());
1084 }
1085 }
1086 }
1087 if (isLinked) {
1088 rule = getLinkedStyle(rule);
1089 }
1090 rule.addAttributes(declaration);
1091 }
1092
1093 //
1094 // The following gaggle of methods is used in maintaining the rules from
1095 // the sheet.
1096 //
1097
1098 /**
1099 * Updates the attributes of the rules to reference any related
1100 * rules in {@code ss}.
1101 */
1102 private synchronized void linkStyleSheetAt(StyleSheet ss, int index) {
1103 if (resolvedStyles.size() > 0) {
1104 Enumeration<ResolvedStyle> values = resolvedStyles.elements();
1105 while (values.hasMoreElements()) {
1106 ResolvedStyle rule = values.nextElement();
1107 rule.insertExtendedStyleAt(ss.getRule(rule.getName()),
1108 index);
1109 }
1110 }
1111 }
1112
1113 /**
1114 * Removes references to the rules in {@code ss}.
1115 * {@code index} gives the index the StyleSheet was at, that is
1116 * how many StyleSheets had been added before it.
1117 */
1118 private synchronized void unlinkStyleSheet(StyleSheet ss, int index) {
1119 if (resolvedStyles.size() > 0) {
1120 Enumeration<ResolvedStyle> values = resolvedStyles.elements();
1121 while (values.hasMoreElements()) {
1122 ResolvedStyle rule = values.nextElement();
1123 rule.removeExtendedStyleAt(index);
1124 }
1125 }
1126 }
1127
1128 /**
1129 * Returns the simple selectors that comprise selector.
1130 */
1131 /* protected */
1132 String[] getSimpleSelectors(String selector) {
1133 selector = cleanSelectorString(selector);
1134 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1135 @SuppressWarnings("unchecked")
1290 /**
1291 * Returns the style that linked attributes should be added to. This
1292 * will create the style if necessary.
1293 */
1294 private Style getLinkedStyle(Style localStyle) {
1295 // NOTE: This is not synchronized, and the caller of this does
1296 // not synchronize. There is the chance for one of the callers to
1297 // overwrite the existing resolved parent, but it is quite rare.
1298 // The reason this is left like this is because setResolveParent
1299 // will fire a ChangeEvent. It is really, REALLY bad for us to
1300 // hold a lock when calling outside of us, it may cause a deadlock.
1301 Style retStyle = (Style)localStyle.getResolveParent();
1302 if (retStyle == null) {
1303 retStyle = addStyle(null, null);
1304 localStyle.setResolveParent(retStyle);
1305 }
1306 return retStyle;
1307 }
1308
1309 /**
1310 * Returns the resolved style for {@code selector}. This will
1311 * create the resolved style, if necessary.
1312 */
1313 private synchronized Style getResolvedStyle(String selector,
1314 Vector<Element> elements,
1315 HTML.Tag t) {
1316 Style retStyle = resolvedStyles.get(selector);
1317 if (retStyle == null) {
1318 retStyle = createResolvedStyle(selector, elements, t);
1319 }
1320 return retStyle;
1321 }
1322
1323 /**
1324 * Returns the resolved style for {@code selector}. This will
1325 * create the resolved style, if necessary.
1326 */
1327 private synchronized Style getResolvedStyle(String selector) {
1328 Style retStyle = resolvedStyles.get(selector);
1329 if (retStyle == null) {
1330 retStyle = createResolvedStyle(selector);
1331 }
1332 return retStyle;
1333 }
1334
1335 /**
1336 * Adds {@code mapping} to {@code elements}. It is added
1337 * such that {@code elements} will remain ordered by
1338 * specificity.
1339 */
1340 private void addSortedStyle(SelectorMapping mapping, Vector<SelectorMapping> elements) {
1341 int size = elements.size();
1342
1343 if (size > 0) {
1344 int specificity = mapping.getSpecificity();
1345
1346 for (int counter = 0; counter < size; counter++) {
1347 if (specificity >= elements.elementAt(counter).getSpecificity()) {
1348 elements.insertElementAt(mapping, counter);
1349 return;
1350 }
1351 }
1352 }
1353 elements.addElement(mapping);
1354 }
1355
1356 /**
1357 * Adds {@code parentMapping} to {@code styles}, and
1358 * recursively calls this method if {@code parentMapping} has
1359 * any child mappings for any of the Elements in {@code elements}.
1360 */
1361 private synchronized void getStyles(SelectorMapping parentMapping,
1362 Vector<SelectorMapping> styles,
1363 String[] tags, String[] ids, String[] classes,
1364 int index, int numElements,
1365 Hashtable<SelectorMapping, SelectorMapping> alreadyChecked) {
1366 // Avoid desending the same mapping twice.
1367 if (alreadyChecked.contains(parentMapping)) {
1368 return;
1369 }
1370 alreadyChecked.put(parentMapping, parentMapping);
1371 Style style = parentMapping.getStyle();
1372 if (style != null) {
1373 addSortedStyle(parentMapping, styles);
1374 }
1375 for (int counter = index; counter < numElements; counter++) {
1376 String tagString = tags[counter];
1377 if (tagString != null) {
1378 SelectorMapping childMapping = parentMapping.
1379 getChildSelectorMapping(tagString, false);
1400 String idName = ids[counter];
1401 childMapping = parentMapping.getChildSelectorMapping(
1402 tagString + "#" + idName, false);
1403 if (childMapping != null) {
1404 getStyles(childMapping, styles, tags, ids, classes,
1405 counter + 1, numElements, alreadyChecked);
1406 }
1407 childMapping = parentMapping.getChildSelectorMapping(
1408 "#" + idName, false);
1409 if (childMapping != null) {
1410 getStyles(childMapping, styles, tags, ids, classes,
1411 counter + 1, numElements, alreadyChecked);
1412 }
1413 }
1414 }
1415 }
1416 }
1417
1418 /**
1419 * Creates and returns a Style containing all the rules that match
1420 * {@code selector}.
1421 */
1422 private synchronized Style createResolvedStyle(String selector,
1423 String[] tags,
1424 String[] ids, String[] classes) {
1425 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1426 @SuppressWarnings("unchecked")
1427 Vector<SelectorMapping> tempVector = sb.getVector();
1428 @SuppressWarnings("unchecked")
1429 Hashtable<SelectorMapping, SelectorMapping> tempHashtable = sb.getHashtable();
1430 // Determine all the Styles that are appropriate, placing them
1431 // in tempVector
1432 try {
1433 SelectorMapping mapping = getRootSelectorMapping();
1434 int numElements = tags.length;
1435 String tagString = tags[0];
1436 SelectorMapping childMapping = mapping.getChildSelectorMapping(
1437 tagString, false);
1438 if (childMapping != null) {
1439 getStyles(childMapping, tempVector, tags, ids, classes, 1,
1440 numElements, tempHashtable);
1483 AttributeSet attr = linkedStyleSheets.elementAt(counter).getRule(selector);
1484 if (attr == null) {
1485 attrs[counter + numStyles] = SimpleAttributeSet.EMPTY;
1486 }
1487 else {
1488 attrs[counter + numStyles] = attr;
1489 }
1490 }
1491 ResolvedStyle retStyle = new ResolvedStyle(selector, attrs,
1492 numStyles);
1493 resolvedStyles.put(selector, retStyle);
1494 return retStyle;
1495 }
1496 finally {
1497 SearchBuffer.releaseSearchBuffer(sb);
1498 }
1499 }
1500
1501 /**
1502 * Creates and returns a Style containing all the rules that
1503 * matches {@code selector}.
1504 *
1505 * @param elements a Vector of all the Elements
1506 * the style is being asked for. The
1507 * first Element is the deepest Element, with the last Element
1508 * representing the root.
1509 * @param t the Tag to use for
1510 * the first Element in {@code elements}
1511 */
1512 private Style createResolvedStyle(String selector, Vector<Element> elements,
1513 HTML.Tag t) {
1514 int numElements = elements.size();
1515 // Build three arrays, one for tags, one for class's, and one for
1516 // id's
1517 String tags[] = new String[numElements];
1518 String ids[] = new String[numElements];
1519 String classes[] = new String[numElements];
1520 for (int counter = 0; counter < numElements; counter++) {
1521 Element e = elements.elementAt(counter);
1522 AttributeSet attr = e.getAttributes();
1523 if (counter == 0 && e.isLeaf()) {
1524 // For leafs, we use the second tier attributes.
1525 Object testAttr = attr.getAttribute(t);
1526 if (testAttr instanceof AttributeSet) {
1527 attr = (AttributeSet)testAttr;
1528 }
1529 else {
1530 attr = null;
1547 classes[counter] = null;
1548 }
1549 if (attr.isDefined(HTML.Attribute.ID)) {
1550 ids[counter] = attr.getAttribute(HTML.Attribute.ID).
1551 toString();
1552 }
1553 else {
1554 ids[counter] = null;
1555 }
1556 }
1557 else {
1558 tags[counter] = ids[counter] = classes[counter] = null;
1559 }
1560 }
1561 tags[0] = t.toString();
1562 return createResolvedStyle(selector, tags, ids, classes);
1563 }
1564
1565 /**
1566 * Creates and returns a Style containing all the rules that match
1567 * {@code selector}. It is assumed that each simple selector
1568 * in {@code selector} is separated by a space.
1569 */
1570 private Style createResolvedStyle(String selector) {
1571 SearchBuffer sb = SearchBuffer.obtainSearchBuffer();
1572 // Will contain the tags, ids, and classes, in that order.
1573 @SuppressWarnings("unchecked")
1574 Vector<String> elements = sb.getVector();
1575 try {
1576 boolean done;
1577 int dotIndex = 0;
1578 int spaceIndex;
1579 int poundIndex = 0;
1580 int lastIndex = 0;
1581 int length = selector.length();
1582 while (lastIndex < length) {
1583 if (dotIndex == lastIndex) {
1584 dotIndex = selector.indexOf('.', lastIndex);
1585 }
1586 if (poundIndex == lastIndex) {
1587 poundIndex = selector.indexOf('#', lastIndex);
1588 }
1748
1749 /**
1750 * Returns an instance of SearchBuffer. Be sure and issue
1751 * a releaseSearchBuffer when done with it.
1752 */
1753 static SearchBuffer obtainSearchBuffer() {
1754 SearchBuffer sb;
1755 try {
1756 if(!searchBuffers.empty()) {
1757 sb = searchBuffers.pop();
1758 } else {
1759 sb = new SearchBuffer();
1760 }
1761 } catch (EmptyStackException ese) {
1762 sb = new SearchBuffer();
1763 }
1764 return sb;
1765 }
1766
1767 /**
1768 * Adds {@code sb} to the stack of SearchBuffers that can
1769 * be used.
1770 */
1771 static void releaseSearchBuffer(SearchBuffer sb) {
1772 sb.empty();
1773 searchBuffers.push(sb);
1774 }
1775
1776 StringBuffer getStringBuffer() {
1777 if (stringBuffer == null) {
1778 stringBuffer = new StringBuffer();
1779 }
1780 return stringBuffer;
1781 }
1782
1783 Vector getVector() {
1784 if (vector == null) {
1785 vector = new Vector();
1786 }
1787 return vector;
1788 }
2140
2141 if (childtype == null) {
2142 if (type == null) {
2143 // Parent view.
2144 View v = childView.getParent();
2145 HTMLDocument doc = (HTMLDocument)v.getDocument();
2146 if (HTMLDocument.matchNameAttribute(v.getElement().getAttributes(),
2147 HTML.Tag.OL)) {
2148 childtype = CSS.Value.DECIMAL;
2149 } else {
2150 childtype = CSS.Value.DISC;
2151 }
2152 } else {
2153 childtype = type;
2154 }
2155 }
2156 return childtype;
2157 }
2158
2159 /**
2160 * Obtains the starting index from {@code parent}.
2161 */
2162 private void getStart(View parent) {
2163 checkedForStart = true;
2164 Element element = parent.getElement();
2165 if (element != null) {
2166 AttributeSet attr = element.getAttributes();
2167 Object startValue;
2168 if (attr != null && attr.isDefined(HTML.Attribute.START) &&
2169 (startValue = attr.getAttribute
2170 (HTML.Attribute.START)) != null &&
2171 (startValue instanceof String)) {
2172
2173 try {
2174 start = Integer.parseInt((String)startValue);
2175 }
2176 catch (NumberFormatException nfe) {}
2177 }
2178 }
2179 }
2180
2181 /**
2182 * Returns an integer that should be used to render the child at
2183 * {@code childIndex} with. The retValue will usually be
2184 * {@code childIndex} + 1, unless {@code parentView}
2185 * has some Views that do not represent LI's, or one of the views
2186 * has a HTML.Attribute.START specified.
2187 */
2188 private int getRenderIndex(View parentView, int childIndex) {
2189 if (!checkedForStart) {
2190 getStart(parentView);
2191 }
2192 int retIndex = childIndex;
2193 for (int counter = childIndex; counter >= 0; counter--) {
2194 AttributeSet as = parentView.getElement().getElement(counter).
2195 getAttributes();
2196 if (as.getAttribute(StyleConstants.NameAttribute) !=
2197 HTML.Tag.LI) {
2198 retIndex--;
2199 } else if (as.isDefined(HTML.Attribute.VALUE)) {
2200 Object value = as.getAttribute(HTML.Attribute.VALUE);
2201 if (value != null &&
2202 (value instanceof String)) {
2203 try {
2204 int iValue = Integer.parseInt((String)value);
2843 * A subclass of MuxingAttributeSet that implements Style. Currently
2844 * the MutableAttributeSet methods are unimplemented, that is they
2845 * do nothing.
2846 */
2847 // PENDING(sky): Decide what to do with this. Either make it
2848 // contain a SimpleAttributeSet that modify methods are delegated to,
2849 // or change getRule to return an AttributeSet and then don't make this
2850 // implement Style.
2851 @SuppressWarnings("serial") // Same-version serialization only
2852 static class ResolvedStyle extends MuxingAttributeSet implements
2853 Serializable, Style {
2854 ResolvedStyle(String name, AttributeSet[] attrs, int extendedIndex) {
2855 super(attrs);
2856 this.name = name;
2857 this.extendedIndex = extendedIndex;
2858 }
2859
2860 /**
2861 * Inserts a Style into the receiver so that the styles the
2862 * receiver represents are still ordered by specificity.
2863 * {@code style} will be added before any extended styles, that
2864 * is before extendedIndex.
2865 */
2866 synchronized void insertStyle(Style style, int specificity) {
2867 AttributeSet[] attrs = getAttributes();
2868 int maxCounter = attrs.length;
2869 int counter = 0;
2870 for (;counter < extendedIndex; counter++) {
2871 if (specificity > getSpecificity(((Style)attrs[counter]).
2872 getName())) {
2873 break;
2874 }
2875 }
2876 insertAttributeSetAt(style, counter);
2877 extendedIndex++;
2878 }
2879
2880 /**
2881 * Removes a previously added style. This will do nothing if
2882 * {@code style} is not referenced by the receiver.
2883 */
2884 synchronized void removeStyle(Style style) {
2885 AttributeSet[] attrs = getAttributes();
2886
2887 for (int counter = attrs.length - 1; counter >= 0; counter--) {
2888 if (attrs[counter] == style) {
2889 removeAttributeSetAt(counter);
2890 if (counter < extendedIndex) {
2891 extendedIndex--;
2892 }
2893 break;
2894 }
2895 }
2896 }
2897
2898 /**
2899 * Adds {@code s} as one of the Attributesets to look up
2900 * attributes in.
2901 */
2902 synchronized void insertExtendedStyleAt(Style attr, int index) {
2903 insertAttributeSetAt(attr, extendedIndex + index);
2904 }
2905
2906 /**
2907 * Adds {@code s} as one of the AttributeSets to look up
2908 * attributes in. It will be the AttributeSet last checked.
2909 */
2910 synchronized void addExtendedStyle(Style attr) {
2911 insertAttributeSetAt(attr, getAttributes().length);
2912 }
2913
2914 /**
2915 * Removes the style at {@code index} +
2916 * {@code extendedIndex}.
2917 */
2918 synchronized void removeExtendedStyleAt(int index) {
2919 removeAttributeSetAt(extendedIndex + index);
2920 }
2921
2922 /**
2923 * Returns true if the receiver matches {@code selector}, where
2924 * a match is defined by the CSS rule matching.
2925 * Each simple selector must be separated by a single space.
2926 */
2927 protected boolean matches(String selector) {
2928 int sLast = selector.length();
2929
2930 if (sLast == 0) {
2931 return false;
2932 }
2933 int thisLast = name.length();
2934 int sCurrent = selector.lastIndexOf(' ');
2935 int thisCurrent = name.lastIndexOf(' ');
2936 if (sCurrent >= 0) {
2937 sCurrent++;
2938 }
2939 if (thisCurrent >= 0) {
2940 thisCurrent++;
2941 }
2942 if (!matches(selector, sCurrent, sLast, thisCurrent, thisLast)) {
2943 return false;
3067 public void removeAttributes(AttributeSet attributes) {}
3068 public void setResolveParent(AttributeSet parent) {}
3069 public String getName() {return name;}
3070 public void addChangeListener(ChangeListener l) {}
3071 public void removeChangeListener(ChangeListener l) {}
3072 public ChangeListener[] getChangeListeners() {
3073 return new ChangeListener[0];
3074 }
3075
3076 /** The name of the Style, which is the selector.
3077 * This will NEVER change!
3078 */
3079 String name;
3080 /** Start index of styles coming from other StyleSheets. */
3081 private int extendedIndex;
3082 }
3083
3084
3085 /**
3086 * SelectorMapping contains a specifitiy, as an integer, and an associated
3087 * Style. It can also reference children {@code SelectorMapping}s,
3088 * so that it behaves like a tree.
3089 * <p>
3090 * This is not thread safe, it is assumed the caller will take the
3091 * necessary precations if this is to be used in a threaded environment.
3092 */
3093 @SuppressWarnings("serial") // Same-version serialization only
3094 static class SelectorMapping implements Serializable {
3095 public SelectorMapping(int specificity) {
3096 this.specificity = specificity;
3097 }
3098
3099 /**
3100 * Returns the specificity this mapping represents.
3101 */
3102 public int getSpecificity() {
3103 return specificity;
3104 }
3105
3106 /**
3107 * Sets the Style associated with this mapping.
3108 */
3109 public void setStyle(Style style) {
3110 this.style = style;
3111 }
3112
3113 /**
3114 * Returns the Style associated with this mapping.
3115 */
3116 public Style getStyle() {
3117 return style;
3118 }
3119
3120 /**
3121 * Returns the child mapping identified by the simple selector
3122 * {@code selector}. If a child mapping does not exist for
3123 *{@code selector}, and {@code create} is true, a new
3124 * one will be created.
3125 */
3126 public SelectorMapping getChildSelectorMapping(String selector,
3127 boolean create) {
3128 SelectorMapping retValue = null;
3129
3130 if (children != null) {
3131 retValue = children.get(selector);
3132 }
3133 else if (create) {
3134 children = new HashMap<String, SelectorMapping>(7);
3135 }
3136 if (retValue == null && create) {
3137 int specificity = getChildSpecificity(selector);
3138
3139 retValue = createChildSelectorMapping(specificity);
3140 children.put(selector, retValue);
3141 }
3142 return retValue;
3143 }
3144
3145 /**
3146 * Creates a child {@code SelectorMapping} with the specified
3147 * {@code specificity}.
3148 */
3149 protected SelectorMapping createChildSelectorMapping(int specificity) {
3150 return new SelectorMapping(specificity);
3151 }
3152
3153 /**
3154 * Returns the specificity for the child selector
3155 * {@code selector}.
3156 */
3157 protected int getChildSpecificity(String selector) {
3158 // class (.) 100
3159 // id (#) 10000
3160 char firstChar = selector.charAt(0);
3161 int specificity = getSpecificity();
3162
3163 if (firstChar == '.') {
3164 specificity += 100;
3165 }
3166 else if (firstChar == '#') {
3167 specificity += 10000;
3168 }
3169 else {
3170 specificity += 1;
3171 if (selector.indexOf('.') != -1) {
3172 specificity += 100;
3173 }
3174 if (selector.indexOf('#') != -1) {
3175 specificity += 10000;
3247 * Parse the given CSS stream
3248 */
3249 public void parse(URL base, Reader r, boolean parseDeclaration,
3250 boolean isLink) throws IOException {
3251 this.base = base;
3252 this.isLink = isLink;
3253 this.parsingDeclaration = parseDeclaration;
3254 declaration.removeAttributes(declaration);
3255 selectorTokens.removeAllElements();
3256 selectors.removeAllElements();
3257 propertyName = null;
3258 parser.parse(r, this, parseDeclaration);
3259 }
3260
3261 //
3262 // CSSParserCallback methods, public to implement the interface.
3263 //
3264
3265 /**
3266 * Invoked when a valid @import is encountered, will call
3267 * {@code importStyleSheet} if a
3268 * {@code MalformedURLException} is not thrown in creating
3269 * the URL.
3270 */
3271 public void handleImport(String importString) {
3272 URL url = CSS.getURL(base, importString);
3273 if (url != null) {
3274 importStyleSheet(url);
3275 }
3276 }
3277
3278 /**
3279 * A selector has been encountered.
3280 */
3281 public void handleSelector(String selector) {
3282 //class and index selectors are case sensitive
3283 if (!(selector.startsWith(".")
3284 || selector.startsWith("#"))) {
3285 selector = selector.toLowerCase();
3286 }
3287 int length = selector.length();
3288
|