This fixes some small problems with the CSS parser, improves CSS rule
resolving to be closer to CSS2 and adds a couple of mappings from HTML
attributes to CSS attributes.

2006-11-09  Roman Kennke  <[EMAIL PROTECTED]>

        * gnu/javax/swing/text/html/css/CSSParser.java
        (parseRuleset): Use new Selector class.
        (parseValue): Parse multiple anys, not only one.
        (main): Allow stylesheet be specified on the command line.
        Use new Selector class.
        * gnu/javax/swing/text/html/css/CSSParserCallback.java
        (startStatement): Use Selector class.
        * gnu/javax/swing/text/html/css/CSSScanner.java
        (readName): Actually read a character in the loop to avoid
        endless loop.
        * gnu/javax/swing/text/html/css/Length.java
        (getValue): Only multiply when we have a percentage value.
        * gnu/javax/swing/text/html/css/Selector.java:
        New class. Provides handling of CSS selectors.
        * javax/swing/text/html/StyleSheet.java
        (CSSStyle.PREC_AUTHOR_IMPORTANT): New constant field.
        (CSSStyle.PREC_AUTHOR_NORMAL): New constant field.
        (CSSStyle.PREC_NORM): New constant field.
        (CSSStyle.PREC_UA): New constant field.
        (CSSStyle.PREC_USER_IMPORTANT): New constant field.
        (CSSStyle.precedence): New field.
        (CSSStyle.priority): Removed.
        (CSSStyle.selector): New field.
        (CSSStyle.CSSStyle(int,Selector)): Initialize with Selector
        and precendence.
        (CSSStyle.compareTo): Adjusted to use the precedence and
        specificity of the selector.
        (CSSStyleSheetParserCallback.precedence): New field.
        (CSSStyleSheetParserCallback.selector): Removed.
        (CSSStyleSheetParserCallback.style): New field.
        (CSSStyleSheetParserCallback.CSSStyleSheetParserCallback):
        Initialize with precedence.
        (CSSStyleSheetParserCallback.declaration): Don't look up
        existing rule, simply create new one.
        (CSSStyleSheetParserCallback.endStatement): Append style
        to stylesheet.
        (CSSStyleSheetParserCallback.startStatement): Use new Selector
        class.
        (css): Changed to be ArrayList.
        (addRule): Create parser with author-normal precendence.
        (getRule): Fixed implementation.
        (loadRules): Create parser with UA precendence.
        (resolveStyle): Use Selector class for resolving and matching
        stylesheet rules.
        (translateHTMLToCSS): Added mappings for a couple of HTML
        attributes.

/Roman

Index: gnu/javax/swing/text/html/css/CSSParser.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/javax/swing/text/html/css/CSSParser.java,v
retrieving revision 1.1
diff -u -1 -5 -r1.1 CSSParser.java
--- gnu/javax/swing/text/html/css/CSSParser.java	21 Aug 2006 20:45:04 -0000	1.1
+++ gnu/javax/swing/text/html/css/CSSParser.java	9 Nov 2006 16:30:51 -0000
@@ -27,30 +27,32 @@
 permission to link this library with independent modules to produce an
 executable, regardless of the license terms of these independent
 modules, and to copy and distribute the resulting executable under
 terms of your choice, provided that you also meet, for each linked
 independent module, the terms and conditions of the license of that
 module.  An independent module is a module which is not derived from
 or based on this library.  If you modify this library, you may extend
 this exception to your version of the library, but you are not
 obligated to do so.  If you do not wish to do so, delete this
 exception statement from your version. */
 
 
 package gnu.javax.swing.text.html.css;
 
 import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
 
 /**
  * A parser for CSS stylesheets.
  *
  * This parser is based on the simple CSS grammar describe in
  *
  * http://www.w3.org/TR/CSS21/syndata.html .
  *
  * @author Roman Kennke ([EMAIL PROTECTED])
  */
 // TODO: Maybe use more restrictive grammar:
@@ -144,31 +146,31 @@
   }
 
   /**
    * Parses a CSS rule set.
    *
    * @return <code>true</code> if the ruleset could be parsed successfully,
    *         <code>false</code> otherwise
    * 
    * @throws IOException if an IO or parse error occurs
    */
   private boolean parseRuleset()
     throws IOException
   {
     StringBuilder selector = new StringBuilder();
     parseSelector(selector);
-    callback.startStatement(selector.toString());
+    callback.startStatement(new Selector(selector.toString()));
     // Read any number of whitespace.
     int token;
     do
       {
         token = readToken();
       } while (token == CSSScanner.S);
     boolean ret = true;
 
     if (token == CSSScanner.CURLY_LEFT)
       {
         // Read any number of whitespace.
         do
           {
             token = readToken();
           } while (token == CSSScanner.S);
@@ -284,31 +286,33 @@
 
   /**
    * Parses a property value.
    *
    * @param s the string builder to read the value into
    *
    * @return <code>true</code> if the ruleset could be parsed successfully,
    *         <code>false</code> otherwise
    * 
    * @throws IOException if an IO or parse error occurs
    */
   private boolean parseValue(StringBuilder s)
     throws IOException
   {
     // FIXME: Handle block and ATKEYWORD.
-    return parseAny(s);
+    boolean success = parseAny(s);
+    while (parseAny(s));
+    return success;
   }
 
   /**
    * Parses a selector.
    *
    * @param sel the string buffer to put the selector into
    *
    * @return <code>true</code> if the ruleset could be parsed successfully,
    *         <code>false</code> otherwise
    * 
    * @throws IOException if an IO or parse error occurs
    */
   private boolean parseSelector(StringBuilder sel)
     throws IOException
   {
@@ -427,37 +431,46 @@
       }
     else
       token = lookahead;
     return token;
   }
 
   /**
    * For testing, we read in the default.css in javax/swing/text/html
    *
    * @param args
    */
   public static void main(String[] args)
   {
     try
       {
-        String name = "/javax/swing/text/html/default.css";
-        InputStream in = CSSScanner.class.getResourceAsStream(name);
+        InputStream in;
+        if (args.length > 0)
+          {
+            File file = new File(args[0]);
+            in = new FileInputStream(file);
+          }
+        else
+          {
+            String name = "/javax/swing/text/html/default.css";
+            in = CSSScanner.class.getResourceAsStream(name);
+          }
         BufferedInputStream bin = new BufferedInputStream(in);
         InputStreamReader r = new InputStreamReader(bin);
         CSSParserCallback cb = new CSSParserCallback()
         {
-          public void startStatement(String selector)
+          public void startStatement(Selector selector)
           {
             System.out.println("startStatement: " + selector);
           }
           public void endStatement()
           {
             System.out.println("endStatement");
           }
           public void declaration(String property, String value)
           {
             System.out.println("declaration: " + property + ", " + value);
           }
         };
         CSSParser p = new CSSParser(r, cb);
         p.parse();
       }
Index: gnu/javax/swing/text/html/css/CSSParserCallback.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/javax/swing/text/html/css/CSSParserCallback.java,v
retrieving revision 1.1
diff -u -1 -5 -r1.1 CSSParserCallback.java
--- gnu/javax/swing/text/html/css/CSSParserCallback.java	21 Aug 2006 20:45:04 -0000	1.1
+++ gnu/javax/swing/text/html/css/CSSParserCallback.java	9 Nov 2006 16:30:51 -0000
@@ -50,31 +50,31 @@
   /**
    * Signals the beginning of a statement.
    *
    * A CSS statement is build up like follows:
    * <pre>
    * <selector> {
    *   ... declarations...
    * }
    * </pre>
    *
    * After startStatement(), the callback will receive zero to n callbacks
    * to declaration, followed by an endStatement() call.
    *
    * @param selector the selector of the statement.
    */
-  void startStatement(String selector);
+  void startStatement(Selector selector);
 
   /**
    * Signals the end of a statement.
    */
   void endStatement();
 
   /**
    * Signals the parsing of one declaration, which defines a mapping
    * from a property to a value.
    *
    * @param property the property
    * @param value the value
    */
   void declaration(String property, String value);
 
Index: gnu/javax/swing/text/html/css/CSSScanner.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/javax/swing/text/html/css/CSSScanner.java,v
retrieving revision 1.4
diff -u -1 -5 -r1.4 CSSScanner.java
--- gnu/javax/swing/text/html/css/CSSScanner.java	21 Aug 2006 20:45:04 -0000	1.4
+++ gnu/javax/swing/text/html/css/CSSScanner.java	9 Nov 2006 16:30:51 -0000
@@ -497,30 +497,31 @@
            || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')))
       {
         parseBuffer[tokenEnd] = (char) ch;
         tokenEnd++;
       }
     else
       throw new CSSLexicalException("Invalid name");
 
     // Read any number (at least one) of [_a-zA-Z0-9-] chars.
     ch = read();
     while (ch != -1 && (ch == '_' || ch == '-' || (ch >= 'a' && ch <= 'z')
            || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9')))
       {
         parseBuffer[tokenEnd] = (char) ch;
         tokenEnd++;
+        ch = read();
       }
 
     // Push back last read character since it doesn't belong to the IDENT.
     lookahead[0] = ch;
   }
 
   /**
    * Reads in a string.
    *
    * @throws IOException
    */
   private void readString()
     throws IOException
   {
     int ch1 = read();
Index: gnu/javax/swing/text/html/css/Length.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/javax/swing/text/html/css/Length.java,v
retrieving revision 1.3
diff -u -1 -5 -r1.3 Length.java
--- gnu/javax/swing/text/html/css/Length.java	7 Nov 2006 12:57:12 -0000	1.3
+++ gnu/javax/swing/text/html/css/Length.java	9 Nov 2006 16:30:51 -0000
@@ -96,32 +96,35 @@
    * @return the value converted to pixels
    */
   public float getValue()
   {
     return floatValue;
   }
 
   /**
    * Returns the absolute span for the case when this length value is
    * a relative value.
    *
    * @param span the target span
    *
    * @return the absolute span
    */
-  public float getValue(float span)
+  public float getValue(float available)
   {
-    return span * floatValue;
+    float span = floatValue;
+    if (isPercentage)
+      span *= available;
+    return span;
   }
 
   /**
    * Returns <code>true</code> when the length value is a percentage
    * value, <code>false</code> otherwise.
    *
    * @return <code>true</code> when the length value is a percentage
    *         value, <code>false</code> otherwise
    */
   public boolean isPercentage()
   {
     return isPercentage;
   }
 }
Index: gnu/javax/swing/text/html/css/Selector.java
===================================================================
RCS file: gnu/javax/swing/text/html/css/Selector.java
diff -N gnu/javax/swing/text/html/css/Selector.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ gnu/javax/swing/text/html/css/Selector.java	9 Nov 2006 16:30:51 -0000
@@ -0,0 +1,233 @@
+/* Selector.java -- A CSS selector
+   Copyright (C) 2006 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.javax.swing.text.html.css;
+
+import java.util.StringTokenizer;
+
+/**
+ * A CSS selector. This provides methods to interpret a selector and
+ * query matches with an actual HTML element tree. 
+ */
+public class Selector
+{
+
+  /**
+   * The actual selector. The selector tokens are stored backwards, that
+   * is the last token first. This makes matching easier.
+   */
+  private String[] selector;
+
+  private String[] elements;
+  private String[] ids;
+  private String[] classes;
+
+  /**
+   * The specificity of the selector.
+   */
+  private int specificity;
+
+  /**
+   * An implicit selector has true here. This is the case for CSS rules that
+   * are attached to HTML elements directly via style="<CSS rule>".
+   */
+  private boolean implicit;
+
+  /**
+   * Creates a new Selector instance for the specified selector string.
+   *
+   * @param sel the selector
+   */
+  public Selector(String sel)
+  {
+    StringTokenizer selectorTokens = new StringTokenizer(sel, " ");
+    selector = new String[selectorTokens.countTokens()];
+    for (int i = selector.length - 1; selectorTokens.hasMoreTokens(); i--)
+      {
+        selector[i] = selectorTokens.nextToken();
+      }
+    calculateSpecificity();
+  }
+
+  /**
+   * Determines if this selector matches the element path specified in the
+   * arguments. The arguments hold the element names as well as class
+   * and id attibutes of the HTML element to be queried. The first item
+   * in the array is the deepest element and the last on the highest up (for
+   * instance, the html tag).
+   *
+   * @param tags
+   * @param classes
+   * @param ids
+   *
+   * @return <code>true</code> when this selector matches the element path,
+   *         <code>false</code> otherwise
+   */
+  public boolean matches(String[] tags, String[] pathClasses, String[] pathIds)
+  {
+    // TODO: This implements class, id and descendent matching. These are
+    // the most commonly used selector matchers in CSS together with HTML.
+    // However, the CSS spec defines a couple of more sophisticated matches
+    // which should be implemented.
+    // http://www.w3.org/TR/CSS21/selector.html
+    
+    // All parts of the selector must match at some point.
+    boolean match = false;
+    int numTags = tags.length;
+    int numSel = selector.length;
+    if (numSel <= numTags)
+      {
+        match = true;
+        int tagIndex = 0;
+        for (int j = 0; j < numSel && match; j++)
+          {
+            boolean tagMatch = false;
+            for (; tagIndex < numTags && tagMatch == false; tagIndex++)
+              {
+                String tag = elements[j];
+                String clazz = classes[j];
+                String id = ids[j];
+                tagMatch = tag.equals("") || tag.equals("*")
+                           || tag.equals(tags[tagIndex]);
+                tagMatch = tagMatch && (clazz.equals("*")
+                                       || clazz.equals(pathClasses[tagIndex]));
+                tagMatch = tagMatch && (id.equals("*")
+                                        || id.equals(pathIds[tagIndex]));
+                // For the last element in the selector we must not look
+                // further.
+                if (j == 0)
+                  break;
+              }
+            // If we don't come out here with a matching tag, then we're
+            // not matching at all.
+            match = tagMatch;
+          }
+      }
+    return match;
+  }
+
+  /**
+   * Returns the specificity of the selector. This is calculated according
+   * to:
+   * http://www.w3.org/TR/CSS21/cascade.html#specificity
+   *
+   * @return the specificity of the selector
+   */
+  public int getSpecificity()
+  {
+    return specificity;
+  }
+
+  /**
+   * Returns a string representation of the selector. This tries to reconstruct
+   * the original selector as closely as possible.
+   *
+   * @return a string representation of the selector
+   */
+  public String toString()
+  {
+    StringBuilder b = new StringBuilder();
+    for (int i = selector.length - 1; i >= 0; i--)
+      {
+        b.append(selector[i]);
+        if (i > 0)
+          b.append(' ');
+      }
+    return b.toString();
+  }
+
+  /**
+   * Calculates the specificity of the selector. This is calculated according
+   * to:
+   * http://www.w3.org/TR/CSS21/cascade.html#specificity
+   */
+  private void calculateSpecificity()
+  {
+    int a = implicit ? 1 : 0;
+    int b = 0;
+    int c = 0;
+    int d = 0;
+    int numSel = selector.length;
+    elements = new String[numSel];
+    ids = new String[numSel];
+    classes = new String[numSel];
+    for (int i = 0; i < numSel; i++)
+      {
+        String sel = selector[i];
+        int clazzIndex = sel.indexOf('.');
+        int idIndex = sel.indexOf('#');
+        String clazz;
+        if (clazzIndex == -1)
+          {
+            clazz = "*";
+            clazzIndex = sel.length();
+          }
+        else
+          {
+            c++;
+            clazz = sel.substring(clazzIndex + 1,
+                                  idIndex > 0 ? Math.min(idIndex, sel.length())
+                                                         : sel.length());
+          }
+        String id;
+        if (idIndex == -1)
+          {
+            id = "*";
+            idIndex = sel.length();
+          }
+        else
+          {
+            b++;
+            id = sel.substring(idIndex + 1,
+                               clazzIndex > 0 ? Math.min(idIndex, sel.length())
+                                              : sel.length());
+          }
+        String tag = sel.substring(0,
+                                   Math.min(Math.min(clazzIndex, idIndex),
+                                            sel.length()));
+        if (! tag.equals("") && ! tag.equals("*"))
+          d++;
+
+        elements[i] = tag;
+        ids[i] = id;
+        classes[i] = clazz;
+      }
+    // An order of 20 should be enough for everybody.
+    specificity = a * 20 ^ 3 + b * 20 ^ 2 + c * 20 + d;
+  }
+}

Reply via email to