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 gnu.java.lang.CPStringBuilder; 42: 43: import java.util.List; 44: import java.util.Map; 45: import java.util.StringTokenizer; 46: 47: /** 48: * A CSS selector. This provides methods to interpret a selector and 49: * query matches with an actual HTML element tree. 50: */ 51: public class Selector 52: { 53: 54: /** 55: * The actual selector. The selector tokens are stored backwards, that 56: * is the last token first. This makes matching easier. 57: */ 58: private String[] selector; 59: 60: private String[] elements; 61: private String[] ids; 62: private String[] classes; 63: 64: /** 65: * The specificity of the selector. 66: */ 67: private int specificity; 68: 69: /** 70: * An implicit selector has true here. This is the case for CSS rules that 71: * are attached to HTML elements directly via style="<CSS rule>". 72: */ 73: private boolean implicit; 74: 75: /** 76: * Creates a new Selector instance for the specified selector string. 77: * 78: * @param sel the selector 79: */ 80: public Selector(String sel) 81: { 82: StringTokenizer selectorTokens = new StringTokenizer(sel, " "); 83: selector = new String[selectorTokens.countTokens()]; 84: for (int i = selector.length - 1; selectorTokens.hasMoreTokens(); i--) 85: { 86: selector[i] = selectorTokens.nextToken(); 87: } 88: calculateSpecificity(); 89: } 90: 91: /** 92: * Determines if this selector matches the element path specified in the 93: * arguments. The arguments hold the element names as well as class 94: * and id attibutes of the HTML element to be queried. The first item 95: * in the array is the deepest element and the last on the highest up (for 96: * instance, the html tag). 97: * 98: * @param tags 99: * 100: * @return <code>true</code> when this selector matches the element path, 101: * <code>false</code> otherwise 102: */ 103: public boolean matches(String[] tags, List<Map<String,String>> attributes) 104: { 105: // TODO: This implements class, id and descendent matching. These are 106: // the most commonly used selector matchers in CSS together with HTML. 107: // However, the CSS spec defines a couple of more sophisticated matches 108: // which should be implemented. 109: // http://www.w3.org/TR/CSS21/selector.html 110: 111: // All parts of the selector must match at some point. 112: boolean match = false; 113: int numTags = tags.length; 114: int numSel = selector.length; 115: if (numSel <= numTags) 116: { 117: match = true; 118: int tagIndex = 0; 119: for (int j = 0; j < numSel && match; j++) 120: { 121: boolean tagMatch = false; 122: for (; tagIndex < numTags && tagMatch == false; tagIndex++) 123: { 124: Object pathClass = attributes.get(tagIndex).get("class"); 125: // Try pseudo class too. 126: Object pseudoClass = attributes.get(tagIndex).get("_pseudo"); 127: Object dynClass = attributes.get(tagIndex).get("_dynamic"); 128: Object pathId = attributes.get(tagIndex).get("id"); 129: String tag = elements[j]; 130: String clazz = classes[j]; 131: String id = ids[j]; 132: tagMatch = tag.equals("") || tag.equals("*") 133: || tag.equals(tags[tagIndex]); 134: tagMatch = tagMatch && (clazz.equals("*") 135: || clazz.equals(dynClass) 136: || clazz.equals(pseudoClass) 137: || clazz.equals(pathClass)); 138: tagMatch = tagMatch && (id.equals("*") 139: || id.equals(pathId)); 140: // For the last element in the selector we must not look 141: // further. 142: if (j == 0) 143: break; 144: } 145: // If we don't come out here with a matching tag, then we're 146: // not matching at all. 147: match = tagMatch; 148: } 149: } 150: return match; 151: } 152: 153: /** 154: * Returns the specificity of the selector. This is calculated according 155: * to: 156: * http://www.w3.org/TR/CSS21/cascade.html#specificity 157: * 158: * @return the specificity of the selector 159: */ 160: public int getSpecificity() 161: { 162: return specificity; 163: } 164: 165: /** 166: * Returns a string representation of the selector. This tries to reconstruct 167: * the original selector as closely as possible. 168: * 169: * @return a string representation of the selector 170: */ 171: public String toString() 172: { 173: CPStringBuilder b = new CPStringBuilder(); 174: for (int i = selector.length - 1; i >= 0; i--) 175: { 176: b.append(selector[i]); 177: if (i > 0) 178: b.append(' '); 179: } 180: return b.toString(); 181: } 182: 183: /** 184: * Calculates the specificity of the selector. This is calculated according 185: * to: 186: * http://www.w3.org/TR/CSS21/cascade.html#specificity 187: */ 188: private void calculateSpecificity() 189: { 190: int a = implicit ? 1 : 0; 191: int b = 0; 192: int c = 0; 193: int d = 0; 194: int numSel = selector.length; 195: elements = new String[numSel]; 196: ids = new String[numSel]; 197: classes = new String[numSel]; 198: for (int i = 0; i < numSel; i++) 199: { 200: String sel = selector[i]; 201: int clazzIndex = sel.indexOf('.'); 202: // Try pseudo class too. 203: if (clazzIndex == -1) 204: clazzIndex = sel.indexOf(':'); 205: int idIndex = sel.indexOf('#'); 206: String clazz; 207: if (clazzIndex == -1) 208: { 209: clazz = "*"; 210: clazzIndex = sel.length(); 211: } 212: else 213: { 214: c++; 215: clazz = sel.substring(clazzIndex + 1, 216: idIndex > 0 ? Math.min(idIndex, sel.length()) 217: : sel.length()); 218: } 219: String id; 220: if (idIndex == -1) 221: { 222: id = "*"; 223: idIndex = sel.length(); 224: } 225: else 226: { 227: b++; 228: id = sel.substring(idIndex + 1, 229: clazzIndex > 0 ? Math.min(clazzIndex, sel.length()) 230: : sel.length()); 231: } 232: String tag = sel.substring(0, 233: Math.min(Math.min(clazzIndex, idIndex), 234: sel.length())); 235: if (! tag.equals("") && ! tag.equals("*")) 236: d++; 237: 238: elements[i] = tag; 239: ids[i] = id; 240: classes[i] = clazz; 241: } 242: // An order of 20 should be enough for everybody. 243: specificity = a * 20 ^ 3 + b * 20 ^ 2 + c * 20 + d; 244: } 245: }