Skip to content

Commit

Permalink
Implement querySelectorAll for element selection
Browse files Browse the repository at this point in the history
  • Loading branch information
sammycage committed Feb 5, 2025
1 parent 50d86d7 commit 23ec1fe
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 5 deletions.
9 changes: 9 additions & 0 deletions include/lunasvg.h
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,8 @@ class LUNASVG_API Element : public Node {
friend class Document;
};

using ElementList = std::vector<Element>;

class SVGRootElement;

class LUNASVG_API Document {
Expand Down Expand Up @@ -688,6 +690,13 @@ class LUNASVG_API Document {
*/
void applyStyleSheet(const std::string& content);

/**
* @brief Selects all elements that match the given CSS selector(s).
* @param content A string containing the CSS selector(s) to match elements.
* @return A list of elements matching the selector(s).
*/
ElementList querySelectorAll(const std::string& content) const;

/**
* @brief Returns the intrinsic width of the document in pixels.
* @return The width of the document.
Expand Down
40 changes: 35 additions & 5 deletions source/svgparser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -461,11 +461,11 @@ static bool parseCombinator(std::string_view& input, SimpleSelector::Combinator&
input.remove_prefix(1);
}

if(skipDelimiter(input, '>'))
if(skipDelimiterAndOptionalSpaces(input, '>'))
combinator = SimpleSelector::Combinator::Child;
else if(skipDelimiter(input, '+'))
else if(skipDelimiterAndOptionalSpaces(input, '+'))
combinator = SimpleSelector::Combinator::DirectAdjacent;
else if(skipDelimiter(input, '~'))
else if(skipDelimiterAndOptionalSpaces(input, '~'))
combinator = SimpleSelector::Combinator::InDirectAdjacent;
return combinator != SimpleSelector::Combinator::None;
}
Expand All @@ -479,7 +479,7 @@ static bool parseSelector(std::string_view& input, Selector& selector)
if(!parseSimpleSelector(input, simpleSelector, failed))
return !failed && (combinator == SimpleSelector::Combinator::Descendant);
selector.push_back(std::move(simpleSelector));
} while(parseCombinator(input, combinator) && skipOptionalSpaces(input));
} while(parseCombinator(input, combinator));
return true;
}

Expand All @@ -490,7 +490,7 @@ static bool parseSelectors(std::string_view& input, SelectorList& selectors)
if(!parseSelector(input, selector))
return false;
selectors.push_back(std::move(selector));
} while(skipDelimiter(input, ',') && skipOptionalSpaces(input));
} while(skipDelimiterAndOptionalSpaces(input, ','));
return true;
}

Expand Down Expand Up @@ -580,6 +580,18 @@ static RuleDataList parseStyleSheet(std::string_view input)
return rules;
}

static SelectorList parseQuerySelectors(std::string_view input)
{
SelectorList selectors;
stripLeadingAndTrailingSpaces(input);
if(!parseSelectors(input, selectors)
|| !input.empty()) {
return SelectorList();
}

return selectors;
}

inline void parseInlineStyle(std::string_view input, SVGElement* element)
{
std::string name;
Expand Down Expand Up @@ -909,4 +921,22 @@ void Document::applyStyleSheet(const std::string& content)
}
}

ElementList Document::querySelectorAll(const std::string& content) const
{
auto selectors = parseQuerySelectors(content);
if(selectors.empty())
return ElementList();
ElementList elements;
m_rootElement->transverse([&](SVGElement* element) {
for(const auto& selector : selectors) {
if(matchSelector(selector, element)) {
elements.push_back(element);
break;
}
}
});

return elements;
}

} // namespace lunasvg
11 changes: 11 additions & 0 deletions source/svgparserutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,17 @@ constexpr bool skipOptionalSpacesOrComma(std::string_view& input)
return skipOptionalSpacesOrDelimiter(input, ',');
}

constexpr bool skipDelimiterAndOptionalSpaces(std::string_view& input, char delimiter)
{
if(!input.empty() && input.front() == delimiter) {
input.remove_prefix(1);
skipOptionalSpaces(input);
return true;
}

return false;
}

constexpr bool skipDelimiter(std::string_view& input, char delimiter)
{
if(!input.empty() && input.front() == delimiter) {
Expand Down

0 comments on commit 23ec1fe

Please sign in to comment.