Re: small selector syntax addition

Daniel Brooks wrote:
> I'd like to propose an addition to the selector syntax that I feel
> would be very useful. I have a feeling that the same effect could be
> accomplished with the all-powerful :matches selector, but I'm not sure.
> 
> What I'd like to be able to do is match an element not at the end of
> the chain, but without a lot of mess. Using parenthesis seems like the
> easiest way to go about it:

The :matches() pseudo-class is exactly designed for this.


> #menu (li) > a[href] { /* match an li inside the #menu that has an
> anchor with an href as a child */ }

#menu li:matches(# > a[href]) { /* ... */ }


The :matches() syntax reduces the ambiguity (what if you have two sets of () in 
the selector), and allows for significantly more powerful constructs (what if 
you want to select and li that has two or more link children?).

However, neither :matches nor it's various less powerful proposed alternatives 
(the fake pseudo ':subject', the inverted combinators '<' or '-', the wrapping 
brackets '(...)', etc) are likely to appear in the Selectors module soon, 
because they all result in a _significant_ performance hit on any dynamic 
implementation of a selector matching engine.

Note that if such a mechanism was to be introduced, :matches() would IMHO be the 
best way to do it. There have basically been three proposals over the years. 
Here is a brief description of each (yes, there are four listed below -- the 
last two are both variations on :matches()):

:subject and (...)

    These mark the simple selector in the chain that should be the subject of
    the selector, assuming that the selector describes the document.

    Examples:

       /* select gardens in the village containing at least one pot plant */
       garden:subject plant[type=pot]
       village (garden) plant[type=pot]

       /* select gardens in the village containing at least one infected plant */
       village garden:subject plant > flea
       village (garden) plant > flea

       /* select gardens in the village containing at least two plants */
       village garden:subject plant ~ plant
       village (garden) plant ~ plant

       /* select gardens containing at least two infected plants */
       /* can't be done */

       /* select all plants that are before infected plants */
       plant:subject + plant > flea
       (plant) + plant > flea

       /* select all clean plants that are before infected plants */
       /* can't be done */

       /* select all fleas if the document contains an antiflea element */
       /* can't be done */

    Advantages:

       1. Reasonably simple.

    Disadvantages:

       1. Changes one of the basic concepts of CSS, that the subject of the
          selector is the last simple selector in the chain.

       2. Can only have one description on the right hand side.

       3. (specifically :subject) uses the wrong syntax -- it's not a
          pseudo-class.

          (specifically (...)) removes the potential use of brackets from the
          language for future extensions.

       4. Ambiguous: what does it mean if there are two :subjects in a selector?

       5. Can't describe everything.

       6. Costly in a dynamic environment.


<, -

    These indicate that the selector should be matched "backwards" up the tree
    rather than down the tree as normal.

    Examples (assuming the opposite of ' ' is '<<'):

       /* select gardens in the village containing at least one pot plant */
       village plant[type=pot] << garden

       /* select gardens in the village containing at least one infected plant */
       village flea < plant << garden

       /* select gardens in the village containing at least two plants */
       village plant ~ plant << garden:subject

       /* select gardens containing at least two infected plants */
       village plant > flea < plant ~ plant > flea << garden

       /* select all plants that are before infected plants */
       flea < plant - plant

       /* select all clean plants that are before infected plants */
       /* can't be done */

       /* select all fleas if the document contains an antiflea element */
       /* can't be done */

    Advantages:

       1. Reuses an existing idiom.

    Disadvantages:

       1. Makes your head spin trying to write such selectors.

       2. Uses up a whole bunch of symbols.

       3. Still can't describe everything.

       4. Costly in a dynamic environment.


:matches() -- the pure proposal

    This a psuedo-class taking a selector to match against the document. The
    pseudo-class matches the element if its argument selector matches a node in
    the document. The special symbol '#' in the argument in a selector that
    matches the node that the pseudo-class is being tested on.

    Examples:

       /* select gardens in the village containing at least one pot plant */
       garden:matches(# plant[type=pot])

       /* select gardens in the village containing at least one infected plant */
       village garden:matches(# plant > flea)

       /* select gardens in the village containing at least two plants */
       village garden:matches(# plant ~ plant)

       /* select gardens containing at least two infected plants */
       village garden:matches(# plant:matches(# > flea) ~ plant > flea)

       /* select all plants that are before infected plants */
       plant:matches(# + plant > flea)

       /* select all clean plants that are before infected plants */
       plant:matches(# + plant > flea):not(:matches(# > flea))

       /* select all fleas if the document contains an antiflea element */
       flea:matches(antiflea)

    Advantages:

       1. Reuses existing idioms.

       2. Reasonably simple conceptual model.

       3. Can be used to to make almost any selector imaginable, so it won't need
          to be extended with each subsequent vesion of the spec.

    Disadvantages:

       1. The most common case is not the easiest to write.

       2. Costly in a dynamic environment.


:matches() and :has() -- the concrete proposal

    :matches in this case is exactly the same as in the previous case, and :has()
    is a simple alias to :matches(), so that

       :has(FOO)

    ...is exactly equivalent to

       :matches(# FOO)

    ...in terms of interpretation as a selector. This is the same as the way we
    have 'odd' and 'even' as equivalents to '2n+1' and '2n' for the :nth-child()
    pseudo-classes. It makes the simple case simple.

    Examples:

       /* select gardens in the village containing at least one pot plant */
       village garden:has(plant[type=pot])

       /* select gardens in the village containing at least one infected plant */
       village garden:has(plant > flea)

       /* select gardens in the village containing at least two plants */
       village garden:has(plant ~ plant)

       /* select gardens containing at least two infected plants */
       village garden:has(plant:has(> flea) ~ plant > flea)

       /* select all plants that are before infected plants */
       plant:has(+ plant > flea)

       /* select all clean plants that are before infected plants */
       plant:not(:has(> flea)):has(+ plant > flea)

       /* select all fleas if the document contains an antiflea element */
       flea:matches(antiflea)

    Advantages:

       1. Reuses existing idioms.

       2. Very simple conceptual model.

       3. Can be used to to make almost any selector imaginable, so it won't need
          to be extended with each subsequent vesion of the spec.

       4. The most common case is the easiest to write.

       5. Reads well: div:has(span)

    Disadvantages:

       1. Costly in a dynamic environment.

-- 
Ian Hickson
``The inability of a user agent to implement part of this specification due to
the limitations of a particular device (e.g., non interactive user agents will
probably not implement dynamic pseudo-classes because they make no sense
without interactivity) does not imply non-conformance.'' -- Selectors, Sec13

Received on Friday, 3 May 2002 10:22:39 UTC