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: * @param classes 97: * @param ids 98: * 99: * @return <code>true</code> when this selector matches the element path, 100: * <code>false</code> otherwise 101: */ 102: public boolean matches(String[] tags, Map[] attributes) 103: { 104: // TODO: This implements class, id and descendent matching. These are 105: // the most commonly used selector matchers in CSS together with HTML. 106: // However, the CSS spec defines a couple of more sophisticated matches 107: // which should be implemented. 108: // http://www.w3.org/TR/CSS21/selector.html 109: 110: // All parts of the selector must match at some point. 111: boolean match = false; 112: int numTags = tags.length; 113: int numSel = selector.length; 114: if (numSel <= numTags) 115: { 116: match = true; 117: int tagIndex = 0; 118: for (int j = 0; j < numSel && match; j++) 119: { 120: boolean tagMatch = false; 121: for (; tagIndex < numTags && tagMatch == false; tagIndex++) 122: { 123: Object pathClass = attributes[tagIndex].get("class"); 124: // Try pseudo class too. 125: Object pseudoClass = attributes[tagIndex].get("_pseudo"); 126: Object dynClass = attributes[tagIndex].get("_dynamic"); 127: Object pathId = attributes[tagIndex].get("id"); 128: String tag = elements[j]; 129: String clazz = classes[j]; 130: String id = ids[j]; 131: tagMatch = tag.equals("") || tag.equals("*") 132: || tag.equals(tags[tagIndex]); 133: tagMatch = tagMatch && (clazz.equals("*") 134: || clazz.equals(dynClass) 135: || clazz.equals(pseudoClass) 136: || clazz.equals(pathClass)); 137: tagMatch = tagMatch && (id.equals("*") 138: || id.equals(pathId)); 139: // For the last element in the selector we must not look 140: // further. 141: if (j == 0) 142: break; 143: } 144: // If we don't come out here with a matching tag, then we're 145: // not matching at all. 146: match = tagMatch; 147: } 148: } 149: return match; 150: } 151: 152: /** 153: * Returns the specificity of the selector. This is calculated according 154: * to: 155: * http://www.w3.org/TR/CSS21/cascade.html#specificity 156: * 157: * @return the specificity of the selector 158: */ 159: public int getSpecificity() 160: { 161: return specificity; 162: } 163: 164: /** 165: * Returns a string representation of the selector. This tries to reconstruct 166: * the original selector as closely as possible. 167: * 168: * @return a string representation of the selector 169: */ 170: public String toString() 171: { 172: StringBuilder b = new StringBuilder(); 173: for (int i = selector.length - 1; i >= 0; i--) 174: { 175: b.append(selector[i]); 176: if (i > 0) 177: b.append(' '); 178: } 179: return b.toString(); 180: } 181: 182: /** 183: * Calculates the specificity of the selector. This is calculated according 184: * to: 185: * http://www.w3.org/TR/CSS21/cascade.html#specificity 186: */ 187: private void calculateSpecificity() 188: { 189: int a = implicit ? 1 : 0; 190: int b = 0; 191: int c = 0; 192: int d = 0; 193: int numSel = selector.length; 194: elements = new String[numSel]; 195: ids = new String[numSel]; 196: classes = new String[numSel]; 197: for (int i = 0; i < numSel; i++) 198: { 199: String sel = selector[i]; 200: int clazzIndex = sel.indexOf('.'); 201: // Try pseudo class too. 202: if (clazzIndex == -1) 203: clazzIndex = sel.indexOf(':'); 204: int idIndex = sel.indexOf('#'); 205: String clazz; 206: if (clazzIndex == -1) 207: { 208: clazz = "*"; 209: clazzIndex = sel.length(); 210: } 211: else 212: { 213: c++; 214: clazz = sel.substring(clazzIndex + 1, 215: idIndex > 0 ? Math.min(idIndex, sel.length()) 216: : sel.length()); 217: } 218: String id; 219: if (idIndex == -1) 220: { 221: id = "*"; 222: idIndex = sel.length(); 223: } 224: else 225: { 226: b++; 227: id = sel.substring(idIndex + 1, 228: clazzIndex > 0 ? Math.min(clazzIndex, sel.length()) 229: : sel.length()); 230: } 231: String tag = sel.substring(0, 232: Math.min(Math.min(clazzIndex, idIndex), 233: sel.length())); 234: if (! tag.equals("") && ! tag.equals("*")) 235: d++; 236: 237: elements[i] = tag; 238: ids[i] = id; 239: classes[i] = clazz; 240: } 241: // An order of 20 should be enough for everybody. 242: specificity = a * 20 ^ 3 + b * 20 ^ 2 + c * 20 + d; 243: } 244: }