Skip to content

Commit

Permalink
Add Rml::Element::Matches function (#573)
Browse files Browse the repository at this point in the history
  • Loading branch information
Paril authored Jan 18, 2024
1 parent 8456240 commit 632a3ad
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Include/RmlUi/Core/Element.h
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,9 @@ class RMLUICORE_API Element : public ScriptInterface, public EnableObserverPtr<E
/// @param[in] selectors The selector or comma-separated selectors to match against.
/// @performance Prefer GetElementById/TagName/ClassName whenever possible.
void QuerySelectorAll(ElementList& elements, const String& selectors);
/// Check if the element matches the given RCSS selector query.
/// @return True if the element matches the given RCSS selector query, false otherwise.
bool Matches(const String& selectors);

//@}

Expand Down
22 changes: 22 additions & 0 deletions Source/Core/Element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1527,6 +1527,28 @@ void Element::QuerySelectorAll(ElementList& elements, const String& selectors)
QuerySelectorAllMatchRecursive(elements, leaf_nodes, this);
}

bool Element::Matches(const String& selectors)
{
StyleSheetNode root_node;
StyleSheetNodeListRaw leaf_nodes = StyleSheetParser::ConstructNodes(root_node, selectors);

if (leaf_nodes.empty())
{
Log::Message(Log::LT_WARNING, "Query selector '%s' is empty. In element %s", selectors.c_str(), GetAddress().c_str());
return false;
}

for (const StyleSheetNode* node : leaf_nodes)
{
if (node->IsApplicable(this))
{
return true;
}
}

return false;
}

EventDispatcher* Element::GetEventDispatcher() const
{
return &meta->event_dispatcher;
Expand Down
8 changes: 8 additions & 0 deletions Source/Lua/Element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ int ElementQuerySelectorAll(lua_State* L, Element* obj)
return 1;
}

int ElementMatches(lua_State* L, Element* obj)
{
const char* tag = luaL_checkstring(L, 1);
lua_pushboolean(L, obj->Matches(tag));
return 1;
}

int ElementHasAttribute(lua_State* L, Element* obj)
{
const char* name = luaL_checkstring(L, 1);
Expand Down Expand Up @@ -594,6 +601,7 @@ RegType<Element> ElementMethods[] = {
RMLUI_LUAMETHOD(Element, GetElementsByTagName),
RMLUI_LUAMETHOD(Element, QuerySelector),
RMLUI_LUAMETHOD(Element, QuerySelectorAll),
RMLUI_LUAMETHOD(Element, Matches),
RMLUI_LUAMETHOD(Element, HasAttribute),
RMLUI_LUAMETHOD(Element, HasChildNodes),
RMLUI_LUAMETHOD(Element, InsertBefore),
Expand Down
1 change: 1 addition & 0 deletions Source/Lua/Element.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ int ElementGetElementById(lua_State* L, Element* obj);
int ElementGetElementsByTagName(lua_State* L, Element* obj);
int ElementQuerySelector(lua_State* L, Element* obj);
int ElementQuerySelectorAll(lua_State* L, Element* obj);
int ElementMatches(lua_State* L, Element* obj);
int ElementHasAttribute(lua_State* L, Element* obj);
int ElementHasChildNodes(lua_State* L, Element* obj);
int ElementInsertBefore(lua_State* L, Element* obj);
Expand Down
33 changes: 33 additions & 0 deletions Tests/Source/UnitTests/Selectors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,22 @@ static const Vector<ClosestSelector> closest_selectors =
{ "D1", ":nth-child(4)", "D" },
{ "D1", "div:nth-child(4)", "P" },
};

struct MatchesSelector {
String id;
String selector;
bool expected_result;
};
static const Vector<MatchesSelector> matches_selectors =
{
{ "X", ".world", false },
{ "X", ".hello", true },
{ "X", ".hello, .world", true },
{ "E", "h3", true },
{ "G", "p#G[class]", true },
{ "G", "p#G[missing]", false },
{ "B", "[unit='m']", true }
};
// clang-format on

// Recursively iterate through 'element' and all of its descendants to find all
Expand Down Expand Up @@ -423,5 +439,22 @@ TEST_CASE("Selectors")
context->UnloadDocument(document);
}

SUBCASE("Matches")
{
const String document_string = doc_begin + doc_end;
ElementDocument* document = context->LoadDocumentFromMemory(document_string);
REQUIRE(document);

for (const MatchesSelector& selector : matches_selectors)
{
Element* start = document->GetElementById(selector.id);
REQUIRE(start);

bool matches = start->Matches(selector.selector);
CHECK_MESSAGE(matches == selector.expected_result, "Matches() selector '" << selector.selector << "' from " << selector.id);
}
context->UnloadDocument(document);
}

TestsShell::ShutdownShell();
}

0 comments on commit 632a3ad

Please sign in to comment.