Frames | No Frames |
1: /* Selector.java -- A CSS selector 2: Copyright (C) 2006 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package gnu.javax.swing.text.html.css; 40: 41: import java.util.Map; 42: import java.util.StringTokenizer; 43: 44: /** 45: * A CSS selector. This provides methods to interpret a selector and 46: * query matches with an actual HTML element tree. 47: */ 48: public class Selector 49: { 50: 51: /** 52: * The actual selector. The selector tokens are stored backwards, that 53: * is the last token first. This makes matching easier. 54: */ 55: private String[] selector; 56: 57: private String[] elements; 58: private String[] ids; 59: private String[] classes; 60: 61: /** 62: * The specificity of the selector. 63: */ 64: private int specificity; 65: 66: /** 67: * An implicit selector has true here. This is the case for CSS rules that 68: * are attached to HTML elements directly via style="<CSS rule>". 69: */ 70: private boolean implicit; 71: 72: /** 73: * Creates a new Selector instance for the specified selector string. 74: * 75: * @param sel the selector 76: */ 77: public Selector(String sel) 78: { 79: StringTokenizer selectorTokens = new StringTokenizer(sel, " "); 80: selector = new String[selectorTokens.countTokens()]; 81: for (int i = selector.length - 1; selectorTokens.hasMoreTokens(); i--) 82: { 83: selector[i] = selectorTokens.nextToken(); 84: } 85: calculateSpecificity(); 86: } 87: 88: /** 89: * Determines if this selector matches the element path specified in the 90: * arguments. The arguments hold the element names as well as class 91: * and id attibutes of the HTML element to be queried. The first item 92: * in the array is the deepest element and the last on the highest up (for 93: * instance, the html tag). 94: * 95: * @param tags 96: * 97: * @return <code>true</code> when this selector matches the element path, 98: * <code>false</code> otherwise 99: */ 100: public boolean matches(String[] tags, Map[] attributes) 101: { 102: // TODO: This implements class, id and descendent matching. These are 103: // the most commonly used selector matchers in CSS together with HTML. 104: // However, the CSS spec defines a couple of more sophisticated matches 105: // which should be implemented. 106: // http://www.w3.org/TR/CSS21/selector.html 107: 108: // All parts of the selector must match at some point. 109: boolean match = false; 110: int numTags = tags.length; 111: int numSel = selector.length; 112: if (numSel <= numTags) 113: { 114: match = true; 115: int tagIndex = 0; 116: for (int j = 0; j < numSel && match; j++) 117: { 118: boolean tagMatch = false; 119: for (; tagIndex < numTags && tagMatch == false; tagIndex++) 120: { 121: Object pathClass = attributes[tagIndex].get("class"); 122: // Try pseudo class too. 123: Object pseudoClass = attributes[tagIndex].get("_pseudo"); 124: Object dynClass = attributes[tagIndex].get("_dynamic"); 125: Object pathId = attributes[tagIndex].get("id"); 126: String tag = elements[j]; 127: String clazz = classes[j]; 128: String id = ids[j]; 129: tagMatch = tag.equals("") || tag.equals("*") 130: || tag.equals(tags[tagIndex]); 131: tagMatch = tagMatch && (clazz.equals("*") 132: || clazz.equals(dynClass) 133: || clazz.equals(pseudoClass) 134: || clazz.equals(pathClass)); 135: tagMatch = tagMatch && (id.equals("*") 136: || id.equals(pathId)); 137: // For the last element in the selector we must not look 138: // further. 139: if (j == 0) 140: break; 141: } 142: // If we don't come out here with a matching tag, then we're 143: // not matching at all. 144: match = tagMatch; 145: } 146: } 147: return match; 148: } 149: 150: /** 151: * Returns the specificity of the selector. This is calculated according 152: * to: 153: * http://www.w3.org/TR/CSS21/cascade.html#specificity 154: * 155: * @return the specificity of the selector 156: */ 157: public int getSpecificity() 158: { 159: return specificity; 160: } 161: 162: /** 163: * Returns a string representation of the selector. This tries to reconstruct 164: * the original selector as closely as possible. 165: * 166: * @return a string representation of the selector 167: */ 168: public String toString() 169: { 170: StringBuilder b = new StringBuilder(); 171: for (int i = selector.length - 1; i >= 0; i--) 172: { 173: b.append(selector[i]); 174: if (i > 0) 175: b.append(' '); 176: } 177: return b.toString(); 178: } 179: 180: /** 181: * Calculates the specificity of the selector. This is calculated according 182: * to: 183: * http://www.w3.org/TR/CSS21/cascade.html#specificity 184: */ 185: private void calculateSpecificity() 186: { 187: int a = implicit ? 1 : 0; 188: int b = 0; 189: int c = 0; 190: int d = 0; 191: int numSel = selector.length; 192: elements = new String[numSel]; 193: ids = new String[numSel]; 194: classes = new String[numSel]; 195: for (int i = 0; i < numSel; i++) 196: { 197: String sel = selector[i]; 198: int clazzIndex = sel.indexOf('.'); 199: // Try pseudo class too. 200: if (clazzIndex == -1) 201: clazzIndex = sel.indexOf(':'); 202: int idIndex = sel.indexOf('#'); 203: String clazz; 204: if (clazzIndex == -1) 205: { 206: clazz = "*"; 207: clazzIndex = sel.length(); 208: } 209: else 210: { 211: c++; 212: clazz = sel.substring(clazzIndex + 1, 213: idIndex > 0 ? Math.min(idIndex, sel.length()) 214: : sel.length()); 215: } 216: String id; 217: if (idIndex == -1) 218: { 219: id = "*"; 220: idIndex = sel.length(); 221: } 222: else 223: { 224: b++; 225: id = sel.substring(idIndex + 1, 226: clazzIndex > 0 ? Math.min(clazzIndex, sel.length()) 227: : sel.length()); 228: } 229: String tag = sel.substring(0, 230: Math.min(Math.min(clazzIndex, idIndex), 231: sel.length())); 232: if (! tag.equals("") && ! tag.equals("*")) 233: d++; 234: 235: elements[i] = tag; 236: ids[i] = id; 237: classes[i] = clazz; 238: } 239: // An order of 20 should be enough for everybody. 240: specificity = a * 20 ^ 3 + b * 20 ^ 2 + c * 20 + d; 241: } 242: }