[selectors] Yet another attempt at trees

Every so often, people try to come up with solutions to the problem of selecting certain elements at specific levels in the document tree.  These proposals inevitably end up running foul of some problem.  Despite this, I have come up with an approach that I hope might meet with approval.

:ancestors(an+b)
:ancestors(an+b,s)
    an+b             is treated the same as with other structural pseudoclasses, including "even" and "odd".
    s                is any simple identifier except for a pseudo-element, in the same manner as :not().
    :ancestors(an+b) is a shorthand for :ancestors(an+b,*).

This pseudoclass matches elements that have a number of ancestors equal to an+b that match the simple selector s.

Note that unlike the other proposals I have seen to date along these lines :ancestors() as proposed here does not change behavior based on what type selector it is paired with.  Whether it will match with a particular element is thus independent of the rest of the selector as is the case with all other existing pseudoclasses with the exception of the -of-type ones.

Example 1:
    :ancestors(0)
This would match the same element as :root as it is the only element with 0 ancestors.

Example 2:
    h:ancestors(2,section)

This would match 3rd level headings in the current draft of XHTML2 but not 4th or higher level headings as the existing CSS code of:
    section section h
does which requires using another rule of the form:
    section section section h
to set things mostly right.  Mostly I say, because if another style sheet has a rule for just simply h that would have otherwise applied, this rule won't restore what that rule would have done.

Example 3:
    div:ancestors(even,div) {background-color:red}
    div:ancestors{odd,div)  {background-color:green}

This would make nested div's alternate between red and green in their background color with the outermost div (which would have 0 ancestors that are div's) being red.

Possible objection:
How does this help with finding 3rd level headings in the following?

<body>
<h/>
<section>
<h/>
<navigation>
<h/>

It helps somewhat to simplify the task.  Assuming that body, section, and navigation can occur in any order and that level headings should only be advanced when they are encountered, it would take 108 different selectors with existing CSS to specify rules that apply only to third level headings.  (27 of the form S S S h to set the 3rd and greater level headings and 81 more of the form S S S S h to set back the 4th level and higher level headings to what they were before, assuming that the user doesn't have his own style rules for h that get clobbered as a result.)  In contrast, it would take only 10 different selectors of the form h:ancestors(x,body):ancestors(y,section):ancestors(z,navigation) where x+y+z are whole numbers adding up to 3 if this selector were available.

Any further simplification would require implementing something analogous to the oft proposed :matches().  Simpler to leave the door open, as this proposal does, for :ancestors(3,:matches(body,section,navigation)) to being someday usable than to try and duplicate the functionality with :ancestors(3,body,section,navigation) since doing so would make :ancestors(n,body,section,navigation) have the same functionality as :matches(body,section,navigation).

===

Assuming that :ancestors() is adopted as defined here then it would probably make sense to also adopt
    :nth-child(an+b,s)
    :nth-last-child(an+b,s)
and acknowledge
    E:nth-of-type(an+b)
as a shortcut for E:nth-child(an+b,E) and
    E:nth-last-of-type(an+b)
as one for E:nth-last-child(an+b,E).

Note that since these expanded versions of the -child() pseudoclasses could be applied to any simple selector and not just the type selector, it would allow for selectors such as
    li:nth-child(1:focus)
that would apply to the item in a list that had focus and those after it in the list.  I don't believe that this functionaity can be duplicated with existing CSS, but I can see the potential usefulness of having different styling applying to the item before a given list item in contrast to those after the item.

Adding
    :first-child(s)
and
    :last-child(s)
might also be worthwhile, but could also make things more difficult for parsers in that they wouldn't be able to categorize first-child or last-child as either identifier or function without having to check the context. Since their functionality would be available as:
    :nth-child(1,s)
and
    :nth-last-child(1,s)
I doubt if the slight harmonization of forms is worth the extra parser complexity that would be required.

===

The two argument version of :ancestors() should have its specificity depend on as its simple selector argument just as :not(s) does.  However, there should also be an addition to the pseudoclass count in the specificity.  This is part so that :ancestors(an+b) and :ancestors(an+b,*) have the same level of specifity.  Without that extra addition, :ancestors(an+b,*) would actually have a lower specificity than :ancestors(an+b), which seems strange to me. The same reasoning applies to two argument versions of nth-child and nth-last-child if those were to be adopted.

===

What I have proposed here becomes even more useful if :matches() were available, but that statement applies to much more than just this proposal.

Ernest Cline
ernestcline@mindspring.com

Received on Tuesday, 19 July 2005 11:56:41 UTC