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