CSS Selector Parser in JavaScript
Building a CSS selector parser is a fundamental task in web development, often used in libraries for DOM manipulation, testing, and more. This challenge asks you to implement a simplified CSS selector parser in JavaScript that can identify which elements in a hypothetical DOM match a given selector string. Successfully completing this challenge will give you a deeper understanding of how CSS selectors work and how to parse strings effectively.
Problem Description
You are tasked with creating a JavaScript function parseSelector(selector) that takes a CSS selector string as input and returns a function. This returned function, when called with a DOM element as input, should return true if the element matches the selector, and false otherwise. The parser should support the following selectors:
- Tag selectors: e.g.,
div,p,h1 - ID selectors: e.g.,
#myId - Class selectors: e.g.,
.myClass - Combinators:
- Descendant selector: e.g.,
div p(p elements anywhere inside div elements) - Child selector: e.g.,
div > p(p elements that are direct children of div elements)
- Descendant selector: e.g.,
The returned matching function should check if a given DOM element matches the parsed selector. For simplicity, assume the DOM element has the following properties:
tagName: A string representing the element's tag name (e.g., "DIV", "P", "H1"). Case-insensitive.id: A string representing the element's ID.className: A string representing the element's class name.children: An array of child DOM elements.
Expected Behavior:
The parseSelector function should return a function that accurately determines if a given DOM element matches the selector. The matching function should be case-insensitive when comparing tag names.
Examples
Example 1:
Input: parseSelector('div')
Output: function(element) { return element.tagName.toUpperCase() === 'DIV'; }
Explanation: The returned function checks if the element's tag name is "DIV" (case-insensitive).
Example 2:
Input: parseSelector('#myId')
Output: function(element) { return element.id === 'myId'; }
Explanation: The returned function checks if the element's ID is "myId".
Example 3:
Input: parseSelector('.myClass')
Output: function(element) { return element.className === 'myClass'; }
Explanation: The returned function checks if the element's class name is "myClass".
Example 4:
Input: parseSelector('div p')
Output: function(element) {
for (const child of element.children) {
if (child.tagName.toUpperCase() === 'P') {
return true;
}
}
return false;
}
Explanation: The returned function checks if any of the element's children have a tag name of "P".
Example 5:
Input: parseSelector('div > p')
Output: function(element) {
for (const child of element.children) {
if (child.tagName.toUpperCase() === 'P') {
return true;
}
}
return false;
}
Explanation: The returned function checks if any of the element's *direct* children have a tag name of "P".
Constraints
- The selector string will only contain valid CSS selector characters (letters, numbers, periods, hashes, spaces, and greater-than signs).
- The input selector string will not be empty.
- The matching function should be efficient enough to handle reasonably sized DOM elements (up to 100 children).
- The
parseSelectorfunction should handle selectors with multiple parts correctly (e.g.,div > .myClass p). While full CSS selector support is not required, the combination of tag, class, and ID selectors with combinators is expected.
Notes
- You don't need to implement a full CSS parser. Focus on the core selectors and combinators mentioned above.
- Consider using recursion to handle nested selectors.
- Think about how to break down the selector string into its constituent parts.
- The DOM element properties (
tagName,id,className,children) are guaranteed to exist and be of the correct type. - Error handling for invalid selectors is not required. Assume the input is always a valid selector string.
- Prioritize clarity and readability in your code. A well-structured solution is more important than extreme optimization.