Re: small selector syntax addition

fantasai wrote:
 > Ian Hickson wrote:
 >> :matches() in this idea is exactly equivalent to :matches() in my
 >> :proposal, >> after moving any selectors associated with the
 >> :subject of the argument out >> to the level of the :matches()
 >> :pseudo, and replacing the subject with a #.
 > You also wrote:
 >>      /* select all fleas if the document contains an antiflea element */
 >>      flea:matches(antiflea)
 > which indicates otherwise, since in the syntax I describe that would
 > match exactly nothing. They are not equivalent, they are merely
 > similar.

Please reread exactly what I wrote. I stand by my original comment.


 > why should you restrict the ability to change the subject of a
 > selector to the argument of the :matches() pseudo-class? If you have
 > the ability to process it, then let it be used outside--it
 > simplifies the majority of the cases where one would want to use
 > such capabilities as we are discussing.

Fair point I guess.


 >   Now, suppose we were to implement :matches() as you describe it,
 >   and came upon flea:matches(antiflea). If the element in question
 >   matches 'flea', then we have to jump to the root of the document
 >   and scan for antiflea. But if we had flea:matches(# antiflea), we
 >   would stay where we are and continue exploring flea's
 >   relations--and because we are inside a :matches pseudo, we are
 >   able to check elements _later_ in the tree.

Right. This is the source of the performance problems.


 >   None of this behavior applies to selectors outside the :matches()
 >   pseudo--not even the parsing and storing of the argument, which
 >   needs to handle # somehow. How can you say that the code for
 >   handling the main selector could be reused for handling
 >   :matches()'s argument? The processing has similarities, but the
 >   code for handling :matches() is a _superset_ of the normal
 >   selector matching code.

Exactly, it's a superset, ss in a decent implementation it'll reuse
all the code of the normal selector matching code.


 >   Now, if I had a selector written like this:
 >
 >      .section > :matches(h1,h2,h3,h4,h5,h6) + $div > code
 >
 >   The <div> would be handed to the selector matching code, which
 >   would then walk to the <div>'s previous sibling and send that to
 >   be matched against :matches()'s argument, which, being a normal
 >   selector with no additional rules, would be processed with the
 >   same parsing and matching code as the main selector. It's pure
 >   recursion. (If <div> matched against it's prior relations, the
 >   function would then inspect its children for <code>.)

Right. But additional code would have to be added to the normal
selector matching code to support $ and all it entails. So the same
amount of new code needs to be written.


 >>>       6. A partial implementation of just $ will suffice for most needs.
 >> I don't really understand this.
 >
 > It means, if you implement $ and leave :matches() for later, you will
 > take care of the majority of the needed capabilities. The variety of
 > selectors that can be created with just $ and without :matches() may
 > be limited, but they are the most-used cases.

Well, equivalently, in my proposal you can implement :has() and leave
the full :matches() and # til later. However, I am thinking long term,
and so this isn't really relevant IMHO.


 >>>       7. Reads better: .section > $div > code
 >>    .section > div:has( > code)
 >> I don't see that the dollar sign is any more understandable.
 >
 > It's not the dollar sign. It's the fact that the whole path through
 > the tree is on one level.

I don't see that the fact that the whole path through the tree is on
one level is any more understandable.


 >>>          One can see the selector as mapping unbroken path through the
 >>>          tree, branching only when selectors _need_ to branch off.
 >>>              .section > :matches(h1,h2,h3,h4,h5,h6) + $div > code
 >
 > Look at this selector:
 >   .section > :matches(h1,h2,h3,h4,h5,h6) + $div > code
 >
 > If you visually group each sequence of simple selectors into a generic box,
 > you can easily see the entire pattern we are trying to match:
 >
 >   [1] > [2] + {3} > [4]
 >                ^
 > The pattern only branches off through the :matches() pseudo if it's
 > necessary (i.e. you have two or more right-side patterns to match
 > against) or if one, as the author, prefers to read it that way. In
 > your model, it always branches off.

I don't see how that is particularily an advantage or disadvantage,
but if you find it more readable that way, fair enough.


 >> You forgot the following 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, unless you
 >>          use the :matches() form, at which point the dollar syntax is
 >>          redundant.
 >>
 >>       3. removes the potential use of a dollar sign from the language for
 >>          future extensions.
 >>
 >>       4. Ambiguous: what does it mean if there are two $s in a selector?
 >
 > It means the selector is invalid.
 > I think the reason you put that in for :subject was because there was no way
 > to handle multiple reverse relationships, and if you tried by using :subject
 > twice, you'd wind up with an ambiguous, undefined selector.

No, I put it in because anything that makes it easier to have invalid
selectors is a bad idea IMHO.


 > And because of this, it allows you to write
 >   .section > :matches(h1,h2,h3,h4,h5,h6) + div
 > which, as you have said, cannot be converted into your :matches() form.
 >
 > Note that it is not only easier to write than specifying all six
 > variations in full,

Interesting.


 > but it is more efficient--you only match against the div once,
 > instead of six times.

A decent implementation of a selector matching algorithm would have
already optimised for this and thus it would not be an issue.


*time passes, during which Ian tries to rewrite his proposal to
include the $ idea*


It just seems so wrong!

Look at the following selectors, and tell me how they should be
interpreted. (Note that for each one, the $ could equally well be
replaced by :subject or the (...) form, which are merely syntactic
equivalents for the same thing.)

    $h1 > $h1

    $h1::after + h2

    $h1::first-line > span

    $::before + ::after

    $span * ::after

Sorry, but every time I see the $ (or :subject, or (...) form) I just
cringe and get the "wrong answer!" alarm ringing in my head.


Taking your comments into account, here is an updated version of my
proposal.


     :matches() and :has()

     The :matches() psuedo-class takes a comma separated list of
     selectors to match against the element. The pseudo-class matches
     the element if one of its argument selectors matches the node.

     The special symbol '#' in the argument is a placeholder that
     matches the node that the pseudo-class is being tested on. If it
     is not used then the last sequence of simple selectors in the
     selector chain has to match the element. In other words,

        A:matches([B] [C])

     ...is exactly equivalent to:

        A[C]:matches([B] #)

     Note that if both the :matches() argument and the sequence of
     simple selectors have non-univorsal type selectors, then the
     argument cannot match. For example:

        A:matches(B C)

     ...can never match. Using the # form avoids the possibility of
     making this mistake, as it cannot be used with other simple
     selectors. (Note "#[foo]" is not valid.)

     If the # form appears anywhere except the end of an argument, then
     the document must be walked looking for matching elements.

     For instance,

        A:matches(# ~ B)

     ...would mean examining all subsequent siblings of A elements for
     B elements.

     Authors are cautioned that this may can be a costly process and
     are therefore advised to avoid using this form where possible.

     The :has() pseudo-class is an alias for :matches(), as follows:

        :has(FOO1, FOO2, ..., FOOn)

     ...is exactly equivalent to

        :matches(# FOO1, # FOO2, ..., # FOOn)

     ...in terms of interpretation as a selector. Note that the FOOs
     can start with a combinator, if they don't then the descendant
     combinator is assumed. 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.

     Note that the arguments for :matches() and :has() cannot have
     pseudo-elements (as these don't ever match real elements).

     Examples:

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

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

        /* select gardens in a 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 the first line of all plants that are before infected plants */
        plant:has(+ plant > flea)::first-line

        /* 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 */
        :root:matches(antiflea, # antiflea) flea

-- 
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 Sunday, 5 May 2002 14:06:46 UTC