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