CSS Selector Parser
This challenge requires you to build a JavaScript function that can parse a CSS selector string into a structured representation. A well-formed parser is a fundamental building block for many web development tools, including style engines, linters, and JavaScript frameworks that manipulate DOM elements.
Problem Description
Your task is to create a JavaScript function parseCSSSelector(selectorString) that takes a string representing a CSS selector and returns a JavaScript object representing its parsed structure. The parser should handle basic CSS selector types, including:
- Type Selectors:
div,p,a - Class Selectors:
.my-class,.another-class - ID Selectors:
#unique-id - Attribute Selectors:
[type="text"],[data-attribute],[href^="https"] - Combinators:
- Descendant Combinator:
div p(space) - Child Combinator:
ul > li - Adjacent Sibling Combinator:
h1 + p - General Sibling Combinator:
h2 ~ p
- Descendant Combinator:
- Pseudo-classes:
:hover,:nth-child(2n+1)(basic support for simple arguments like numbers, keywords, and simple expressions) - Pseudo-elements:
::before,::after(though strictly speaking, pseudo-elements are not part of selectors, they are often handled by parsers)
The output should be a hierarchical structure that accurately reflects the relationships between the different parts of the selector.
Key Requirements:
- The function must accept a single argument:
selectorString(a string). - The function must return a JavaScript object.
- The structure of the returned object should be consistent and clearly represent the selector's components and their relationships.
- The parser should be robust enough to handle valid CSS selector syntax.
- Handle whitespace appropriately (leading/trailing whitespace in the overall selector and around combinators).
Expected Behavior:
The parser should break down the selector into its constituent parts and group them logically. For instance, a selector like div.my-class > #main p[data-id] should be parsed into a structure that shows:
- A
divelement. - Which has a class
my-class. - This is a parent of another element.
- That element has an ID of
main. - Inside
#main, there's apelement. - Which has an attribute
data-id.
Edge Cases to Consider:
- Empty selector string.
- Selectors with only whitespace.
- Selectors with multiple combinators in sequence (e.g.,
div p > span). - Selectors with complex attribute selectors (e.g.,
[data-attribute="value attribute"]). - Selectors with multiple classes on the same element (e.g.,
.class1.class2). - Pseudo-classes with arguments that need parsing.
Examples
Example 1:
Input: "div.my-class > #main p[data-id]"
Output:
{
"type": "selector",
"combinator": ">",
"left": {
"type": "selector",
"left": {
"type": "type",
"name": "div"
},
"right": {
"type": "class",
"name": "my-class"
}
},
"right": {
"type": "selector",
"combinator": " ", // Descendant combinator
"left": {
"type": "id",
"name": "main"
},
"right": {
"type": "selector",
"left": {
"type": "type",
"name": "p"
},
"right": {
"type": "attribute",
"name": "data-id"
}
}
}
}
Explanation: This output represents the hierarchical structure of the selector. The > combinator indicates a direct child relationship. The descendant combinator (space) shows that the p element is somewhere within the #main element. Each component (type, class, ID, attribute) is clearly identified.
Example 2:
Input: "h1 + p:hover::after"
Output:
{
"type": "selector",
"combinator": "+",
"left": {
"type": "type",
"name": "h1"
},
"right": {
"type": "selector",
"left": {
"type": "type",
"name": "p"
},
"right": {
"type": "pseudo-class",
"name": "hover"
},
"pseudoElement": {
"type": "pseudo-element",
"name": "after"
}
}
}
Explanation: This shows an h1 element followed by an adjacent sibling p element. The p element also has a :hover pseudo-class and a ::after pseudo-element.
Example 3:
Input: ".container .item[data-value='important']"
Output:
{
"type": "selector",
"combinator": " ",
"left": {
"type": "selector",
"left": {
"type": "class",
"name": "container"
},
"right": {
"type": "class",
"name": "item"
}
},
"right": {
"type": "selector",
"left": {
"type": "class",
"name": "item"
},
"right": {
"type": "attribute",
"name": "data-value",
"operator": "=",
"value": "important"
}
}
}
Explanation: This example demonstrates chaining of selectors and an attribute selector with a specific value and operator. Note how multiple classes are parsed as separate components within a selector block.
Constraints
- The input
selectorStringwill be a string. - The
selectorStringwill not contain invalid CSS syntax that would typically cause a browser to ignore the rule (e.g., unmatched parentheses, illegal characters within identifiers). - Assume the input selectors are valid for the scope of this problem, focusing on structural parsing rather than full CSS validation.
- The parser should aim for reasonable performance, but extreme optimization is not the primary goal for this challenge.
Notes
- Consider how you will represent the different types of selector components (e.g., type, class, ID, attribute, combinator, pseudo-class, pseudo-element) in your output object.
- The order of parsing matters. You'll likely want to process combinators to establish the hierarchical relationships.
- Handling whitespace correctly is crucial for accurate parsing of combinators.
- For attribute selectors, you'll need to parse the attribute name, optional operator (e.g.,
=,^=,$=,*=,~=,|=), and the attribute value. - Pseudo-classes and pseudo-elements can have arguments. For this challenge, focus on simple arguments like keywords or basic expressions.