2

Specification

The inputs to this function would be two selectors. This implies that there is no pre-existing DOM element, which is an important constraint in this problem. The question that the function should answer is "would all elements matched by Needle be matched by Haystack?" In other words, I am trying to determine if the set of elements matched by Needle represents a subset of the set of elements matched by Haystack. The following table of examples should clarify the specification:

Needle           | Haystack         | Matches? | Explanation / Counterexample
-----------------+------------------+----------+--------------------------------
div              | *                | yes      | Any div is something.
div              | div              | yes      | Any div is a div.
div              | div:not(.classy) | no       | <div class="classy">
div              | .classy          | no       | <div>
div.classy       | div              | yes      | Any classy div is a div.
div.classy       | div.classy       | yes      | Any classy div is a classy div.
div.classy       | div:not(.classy) | no       | <div class="classy">
div.classy       | .classy          | yes      | Any classy div is classy.
div:not(.classy) | div              | yes      | Any non-classy div is a div.
.classy          | div              | no       | <span>
.classy          | div.classy       | no       | <div>
:not(.classy)    | div              | no       | <span>
div div          | div              | yes      | Any nested div is a div.
div              | div div          | no       | Not all divs are nested.

A naive solution would create a temporary DOM element based on the Needle and call document.querySelector() with the Haystack as argument. The solution would iterate over the list and return true if it finds the generated element. Caveats:

  • Just because one element matching the Needle happens to match the Haystack does not mean all possible elements matching the Needle will match the Haystack. It is difficult to make the representative element as general as possible.
  • It is difficult to generate an element matching a non-simple selector, because the entire tree up to that element would have to be recreated to accomodate it.
  • Inserting temporary elements into the DOM and not properly cleaning them up can interfere with site logic.

A complete solution should avoid creating temporary DOM elements. The solution would ideally rely on the rendering engine's built-in CSS matching mechanisms. If the solution makes use of vendor-specific functions, those functions should be recreatable in pure JavaScript on the current version of other browsers (where IE10 can be considered the "current" IE).


Motivation

The purpose of the function is to query the document stylesheets for all rules that apply to elements that would match a given selector. For example, given the following CSS:

* {
    color: yellow;
}

div {
    background-color: black;
}

.classy {
    margin: 5px;
}

one would expect the following:

Selector   | Matched rules
-----------+-----------------------------------------------------
div.classy | color: yellow; background-color: black; margin: 5px;
div        | color: yellow; background-color: black;
.classy    | color: yellow; margin: 5px;

A naive solution would iterate over the document stylesheets block by block and perform exact string matches on the selectors. These would yield fewer rules than computed by the CSS engine. A complete solution would require the function described in the above section.


Similar questions

I believe this asker wanted a similar solution, but the question was not understood.


Thank you for your time!

Community
  • 1
  • 1
Milosz
  • 2,924
  • 3
  • 22
  • 24
  • You seem to be asking how to determine if one selector represents a subset of another (such as 'div' is a subset of '*'). Some examples have the wrong value for "matches" given the test: "…would **any** element…" matched by one be matched by the other. – RobG Mar 17 '14 at 01:35
  • 1
    I think your question should not be "would any element matched by Needle be matched by Haystack", but rather "are **all** elements matched by needle also matched by haystack". – RobG Mar 17 '14 at 01:41
  • Iterating to the DOM and getting so many information out of it would lead to quite complex code and is not very performance-friendly. I'm not sure what your goal is, but ever thought about moving the needed logic away from the DOM, into a MV*? – Richard Mar 17 '14 at 01:42
  • @RobG you are right, that wording is a bit weasely. I do mean "all". I will edit it. – Milosz Mar 17 '14 at 02:07
  • @Richard in this case, I was inspired to explore the possibility of querying the document stylesheets directly when I wanted to fetch the color of an element that did not exist on the page - straight from the CSS. From there I wanted to know if it could be done in an elegant, efficient, and general way. So, primarily I am just curious, but if there is a good answer it will prove a valuable tool for client-side-only web app development. – Milosz Mar 17 '14 at 02:37
  • That question you link to is asking something entirely different. It involves selectors, but not quite in the same way as your question. – BoltClock Mar 17 '14 at 02:52
  • 2
    Note also that the CSS2 definition of "simple selector" is outdated. [Selectors 3](http://www.w3.org/TR/selectors/#selector-syntax) actually makes things much more confusing, but it does define the current meaning of "simple selector", which is any one component e.g. type, ID, class, attribute, etc, excluding combinators and pseudo-elements. So a "non-simple selector" in your case refers to a selector that is made up of more than one sequence of simple selectors (or a selector that contains at least one combinator). – BoltClock Mar 17 '14 at 02:54
  • 1
    For selectors that only have one sequence of simple selectors, the basic rules are: 1) universal selector is self-explanatory, matches anything 2) where there is no type selector, a universal selector is implied 3) type selectors are always more specific than universal 4) a specific simple selector has to be completely absent from haystack (not negated) in order to ensure that needle will match for that simple selector (this is how subset matches work in Selectors) - this includes negations within needle. – BoltClock Mar 17 '14 at 03:00
  • @BoltClock In the other question's second paragraph ("It works, but...") it sounds like the asker is looking for something that doesn't require a real document tree. Also, you reminded me that I completely forgot to include nested selectors in my example table - I will add one. I like the idea of splitting each selector into its simple components and comparing. I think it holds water. I will make a test bed and give it a shot. – Milosz Mar 17 '14 at 03:07
  • 2
    @Milosz: Yeah, once you start dabbling with complex/nested selectors, well, things get a little more complex (read: **much more complex**). The general rules for those that I can think of are: 1) `E > F` is a subset of `E F` 2) `E + F` is a subset of `E ~ F` 3) look at the rightmost selector sequence first (the subject of the selector); if the subject of needle isn't a subset of the subject of haystack, then you definitely don't have a match. The difficulty is in actually implementing those rules... – BoltClock Mar 17 '14 at 03:09
  • Note also that some pseudo-classes, as well as class and ID selectors, carry document-level semantics which won't allow you to check certain cases without having a reference document. For example, `div.classy` is only equivalent to `div[class~=classy]` in HTML and other languages where the `class` attribute represents space-separated class names. Of course, since your question assumes no reference DOM, you can simply mark such cases as non-matching right away. – BoltClock Mar 17 '14 at 03:13
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/49858/discussion-between-milosz-and-boltclock) – Milosz Mar 17 '14 at 03:14

1 Answers1

2

I don't think this is possible without a document to give the seletors some context. It's easy to see that "div" is a subset of "*". However, it's not so easy for other selectors like "div" and ".classy". They might return exactly the same set of elements if every div has a class of classy and no other element has that class.

Similarly, "div" and "div:not(.classy)" might return exactly the same set if no div has a class of classy.

So in a general case, it's impossible to determine whether one selector returns a subset of another without a document, though it can be done for a very limited (trivial?) set of cases (e.g. span.someClass is a subset of span).

RobG
  • 142,382
  • 31
  • 172
  • 209
  • I'm having trouble coming up with a selector for which this is truly ambiguous (in the general case) without a document. For example, without consulting a real document tree I can safely say that is is possible for "div" to yield something that isn't ".classy" because there can exist a classy span. It might not, but it is possible. On the other hand, I can safely say that a "div:first-child" is in particular a "div", no matter the document tree. Your trivial case gives me an idea: perhaps I can split the selector on `:`, `.`, `#`, etc, sort, and compare? – Milosz Mar 17 '14 at 02:57