Source for gnu.javax.swing.text.html.css.Selector

   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: }