Cascade: Supplement instead of Overwrite

	
SUMMARY:

   Add a way of making a declaration supplement the cascade instead of
   replacing it as now. This would let the following:

     * { counter-increment: elements; } 
     H1 { counter-increment: headers ! supplement; }

   ...be equivalent to the following:

     * { counter-increment: elements; } 
     H1 { counter-increment: headers elements; }


THE PROBLEM:

One problem in CSS at the moment is that the cascade is sometimes a
little too drastic. For example, if you have more than one counter,
then there is strictly no way of setting a counter to count all
elements -- for example,

   * { counter-increment: elements; } 
   H1 { counter-increment: headers; }

...will result in all elements *except* H1s increasing the "elements"
counter.

I mentioned this back when the CSS2 drafts were coming out, but I
could not think of a solution at the time ([1], [2]).

This problem is now getting more and more important, since the
following properties either now take multiple values or have had
multiple-value proposals:

   'counter-increment'
   'counter-reset'
   'string-set'
   'quotes'
   'text-decoration'
   'text-shadow'
   'content' (to cope with broken/unsupported url() inclusions)
   'height'/'width' (to cope with multiple 'content's)
   'font-family'
    etc...


THE SOLUTION:

What we need is a way of indicating that a certain value should not
"overwrite" the "current" value of the property, but should be
appended to it. For example, the example quoted above would currently
result in this cascade for H1 elements:

   origin |  value   | specificity | weight
   -------+----------+-------------+--------
   author | elements |  000        | normal
   author | headers  |  001        | normal

...which would result in the 'counter-increment' property getting the
value "headers" for H1 elements.

However, if we could say that the values should be appended to the
values from other declarations, instead of replacing them, then we
would could make the value of 'counter-increment' first be "headers",
and then add the previous matching declaration's value ("elements"),
which would result in 'counter-increment' having the value "headers
elements", which would allow us to count all the elements.


THE SEMANTICS:

The semantics of the supplemental cascade must of course be defined.
Here is what I believe to be the most useful system:

   1. Sort the applicable rules as per CSS2 section 6.4.1.
   2. Assign the value from the winning declaration to the property.
   3. If that value was marked !supplement, then take the value from
      the rule which would have won, had the winning rule not been
      present, and append it to the property. 
   4. Repeat step 3 until you reach a value which was not marked as
      supplementing the cascade. Append that value to the property.

(For those who manage Apache servers, this is similar to the way that
the Options directive works with +/- prefixes.)


THE SYNTAX:

There are several ways of extending the current CSS syntax in a manner
compatible with the forwards-compatible parsing of CSS1 to specify
that the cascade should be supplemental and not replacing. Here is
one, which uses syntax similar to the proposal for specifying minimum
and maximum values:

   property : values values ! supplement; /* e.g., counter-increment */
   property : values, values ! supplement; /* e.g., font-family */

Another is to fiddle with the colon:

   property +: values values; /* e.g., counter-increment */
   property +: values, values; /* e.g., font-family */

Personally, I prefer the first syntax; it is more CSS-like as there is
a precedent (the !important declaration).

From here on I am going to assume that the !supplement syntax is used,
although of course which syntax is used is not at all as important as
the semantics of the syntax.


SOME EXAMPLES:

Assuming you had stylesheets where the following rules matched a
particular element:

  /* ua.css: */
  X        { property: a; }

  /* author.css: */
  X        { property: b ! important ! supplement; }
  X.class  { property: c; }
  X        { property: d; }
  X#id     { property: e ! supplement; }
  X X      { property: f ! supplement; }

...you would, using the type of tables I've been using above, get the
following results in the cascade:

   origin |  value   | specificity | weight | cascade type
   -------+----------+-------------+--------+--------------
     ua   |    a     |  001        | normal | 
   author |    d     |  001        | normal | 
   author |    f     |  002        | normal | supplement
   author |    c     |  011        | normal | 
   author |    e     |  101        | normal | supplement
   author |    b     |  001        | imprnt | supplement

The first value selected would be "b", which is marked supplemental.
We set the value to this.

The next value selected would be "e". We append this to the current
value. This declaration is also marked supplemental, so we continue:

The next value selected is "c", so we append this to the value. This
declaration was not marked 'supplement' and so we stop here.

The resulting value is therefore "c e b".


POSSIBLE EXTENSIONS:

One possible extension is that instead of !supplement, we use !append
and !prepend, and the values are appended or prepended to the property
as specified. This means, for example, that one could add new inner
quotes as well as new outer quotes:

   Q { quotes: "'" "'"; }
   Q { quotes: "<<" ">>" ! prepend; }
   Q { quotes: '"' '"' ! append; }

...would be functionally equivalent to:

   Q { quotes: "<<" ">>" "'" "'" '"' '"'; }

...except that of course the three rules could be in three different
stylesheets. (The first might be a UA rule, the second a user rule,
and the third an author rule from a very polite author.)


NOTES:

If this proposal is used, then it would then be useful if each
property definition would say whether or not it supported supplemental
cascading:

   'property-name'
        Value:         legal values & syntax
        ...
        Cascading:     whether this property supports supplementary
                       cascading or limits (minimum/maximum values)

For example:

   'counter-reset'
     Value:         [ <identifier> <integer>? ]+ | none | inherit
     Initial:       none
     Applies to:    all elements
     Inherited:     no
     Percentages:   N/A
     Media:         all
     Cascading:     supports supplementary cascading

...but

   'visibility'
     Value:         visible | hidden | collapse | inherit
     Initial:       inherit
     Applies to:    all elements
     Inherited:     no
     Percentages:   N/A
     Media:         visual
     Cascading:     does not support any special cascading

...and

   'height'
     Value:         [ <length> | <percentage> | auto ]+ | inherit
     Initial:       auto
     Applies to:    all elements but non-replaced inline elements, table
                    columns, and column groups
     Inherited:     no
     Percentages:   see prose
     Media:         visual
     Cascading:     supports supplementary cascading and limits

Comments?


--- References ---
[1] http://lists.w3.org/Archives/Public/www-style/1998May/0003.html
[2] http://lists.w3.org/Archives/Public/www-style/1998May/0039.html

-- 
Ian Hickson
: Is your JavaScript ready for Nav5 and IE5?
: Get the latest JavaScript client sniffer at 
: http://developer.netscape.com/docs/examples/javascript/browser_type.html

Received on Wednesday, 6 October 1999 14:52:47 UTC