Browse Source

Implement `Element::QuerySelector` and `Element::QuerySelectorAll`.

Michael Ragazzon 5 years ago
parent
commit
9491549950

+ 12 - 0
Include/RmlUi/Core/Element.h

@@ -524,6 +524,18 @@ public:
 	/// @param[out] elements Resulting elements.
 	/// @param[out] elements Resulting elements.
 	/// @param[in] tag Tag to search for.
 	/// @param[in] tag Tag to search for.
 	void GetElementsByClassName(ElementList& elements, const String& class_name);
 	void GetElementsByClassName(ElementList& elements, const String& class_name);
+	/// Returns the first descendent element matching the RCSS selector query.
+	/// @param[in] selectors The selector or comma-separated selectors to match against.
+	/// @return The first matching element during a depth-first traversal.
+	/// @performance Prefer GetElementById/TagName/ClassName whenever possible.
+	Element* QuerySelector(const String& selector);
+	/// Returns all descendent elements matching the RCSS selector query.
+	/// @param[out] elements The list of matching elements.
+	/// @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);
+
+
 	//@}
 	//@}
 
 
 	/**
 	/**

+ 68 - 0
Source/Core/Element.cpp

@@ -58,6 +58,7 @@
 #include "PropertiesIterator.h"
 #include "PropertiesIterator.h"
 #include "Pool.h"
 #include "Pool.h"
 #include "StyleSheetParser.h"
 #include "StyleSheetParser.h"
+#include "StyleSheetNode.h"
 #include "TransformState.h"
 #include "TransformState.h"
 #include "TransformUtilities.h"
 #include "TransformUtilities.h"
 #include "XMLParseTools.h"
 #include "XMLParseTools.h"
@@ -1482,6 +1483,73 @@ void Element::GetElementsByClassName(ElementList& elements, const String& class_
 	return ElementUtilities::GetElementsByClassName(elements, this, class_name);
 	return ElementUtilities::GetElementsByClassName(elements, this, class_name);
 }
 }
 
 
+static Element* QuerySelectorMatchRecursive(const StyleSheetNodeListRaw& nodes, Element* element)
+{
+	for (int i = 0; i < element->GetNumChildren(); i++)
+	{
+		Element* child = element->GetChild(i);
+
+		for (const StyleSheetNode* node : nodes)
+		{
+			if (node->IsApplicable(child, false))
+				return child;
+		}
+
+		Element* matching_element = QuerySelectorMatchRecursive(nodes, child);
+		if (matching_element)
+			return matching_element;
+	}
+
+	return nullptr;
+}
+
+static void QuerySelectorAllMatchRecursive(ElementList& matching_elements, const StyleSheetNodeListRaw& nodes, Element* element)
+{
+	for (int i = 0; i < element->GetNumChildren(); i++)
+	{
+		Element* child = element->GetChild(i);
+
+		for (const StyleSheetNode* node : nodes)
+		{
+			if (node->IsApplicable(child, false))
+			{
+				matching_elements.push_back(child);
+				break;
+			}
+		}
+
+		QuerySelectorAllMatchRecursive(matching_elements, nodes, child);
+	}
+}
+
+Element* Element::QuerySelector(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 nullptr;
+	}
+
+	return QuerySelectorMatchRecursive(leaf_nodes, this);
+}
+
+void Element::QuerySelectorAll(ElementList& elements, 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;
+	}
+
+	QuerySelectorAllMatchRecursive(elements, leaf_nodes, this);
+}
+
 // Access the event dispatcher
 // Access the event dispatcher
 EventDispatcher* Element::GetEventDispatcher() const
 EventDispatcher* Element::GetEventDispatcher() const
 {
 {

+ 1 - 1
Source/Core/StyleSheet.cpp

@@ -364,7 +364,7 @@ SharedPtr<ElementDefinition> StyleSheet::GetElementDefinition(const Element* ele
 			// trying to match nodes in the element's hierarchy to nodes in the style hierarchy.
 			// trying to match nodes in the element's hierarchy to nodes in the style hierarchy.
 			for (StyleSheetNode* node : nodes)
 			for (StyleSheetNode* node : nodes)
 			{
 			{
-				if (node->IsApplicable(element))
+				if (node->IsApplicable(element, true))
 				{
 				{
 					applicable_nodes.push_back(node);
 					applicable_nodes.push_back(node);
 				}
 				}

+ 16 - 7
Source/Core/StyleSheetNode.cpp

@@ -266,14 +266,23 @@ inline bool StyleSheetNode::MatchStructuralSelector(const Element* element) cons
 }
 }
 
 
 // Returns true if this node is applicable to the given element, given its IDs, classes and heritage.
 // Returns true if this node is applicable to the given element, given its IDs, classes and heritage.
-bool StyleSheetNode::IsApplicable(const Element* const in_element) const
+bool StyleSheetNode::IsApplicable(const Element* const in_element, bool skip_id_tag) const
 {
 {
-	// This function is called with an element that matches a style node only with the tag name and id. We have to determine
-	// here whether or not it also matches the required hierarchy.
-	
-	// First, check locally for matching class and pseudo class. Id and tag have already been checked in StyleSheet.
-	if (!MatchClassPseudoClass(in_element))
-		return false;
+	// Determine whether the element matches the current node and its entire lineage. The entire hierarchy of
+	// the element's document will be considered during the match as necessary.
+
+	if (skip_id_tag)
+	{
+		// Id and tag have already been checked, only check class and pseudo class.
+		if (!MatchClassPseudoClass(in_element))
+			return false;
+	}
+	else
+	{
+		// Id and tag have not already been matched, match everything.
+		if (!Match(in_element))
+			return false;
+	}
 
 
 	const Element* element = in_element;
 	const Element* element = in_element;
 
 

+ 1 - 1
Source/Core/StyleSheetNode.h

@@ -86,7 +86,7 @@ public:
 	const PropertyDictionary& GetProperties() const;
 	const PropertyDictionary& GetProperties() const;
 
 
 	/// Returns true if this node is applicable to the given element, given its IDs, classes and heritage.
 	/// Returns true if this node is applicable to the given element, given its IDs, classes and heritage.
-	bool IsApplicable(const Element* element) const;
+	bool IsApplicable(const Element* element, bool skip_id_tag) const;
 
 
 	/// Returns the specificity of this node.
 	/// Returns the specificity of this node.
 	int GetSpecificity() const;
 	int GetSpecificity() const;

+ 22 - 4
Source/Core/StyleSheetParser.cpp

@@ -362,7 +362,6 @@ int StyleSheetParser::Parse(StyleSheetNode* node, Stream* _stream, const StyleSh
 					StringList rule_name_list;
 					StringList rule_name_list;
 					StringUtilities::ExpandString(rule_name_list, pre_token_str);
 					StringUtilities::ExpandString(rule_name_list, pre_token_str);
 
 
-
 					// Add style nodes to the root of the tree
 					// Add style nodes to the root of the tree
 					for (size_t i = 0; i < rule_name_list.size(); i++)
 					for (size_t i = 0; i < rule_name_list.size(); i++)
 					{
 					{
@@ -505,6 +504,26 @@ bool StyleSheetParser::ParseProperties(PropertyDictionary& parsed_properties, co
 	return success;
 	return success;
 }
 }
 
 
+StyleSheetNodeListRaw StyleSheetParser::ConstructNodes(StyleSheetNode& root_node, const String& selectors)
+{
+	const PropertyDictionary empty_properties;
+
+	StringList selector_list;
+	StringUtilities::ExpandString(selector_list, selectors);
+
+	StyleSheetNodeListRaw leaf_nodes;
+
+	for (const String& selector : selector_list)
+	{
+		StyleSheetNode* leaf_node = ImportProperties(&root_node, selector, empty_properties, 0);
+
+		if (leaf_node != &root_node)
+			leaf_nodes.push_back(leaf_node);
+	}
+
+	return leaf_nodes;
+}
+
 
 
 bool StyleSheetParser::ReadProperties(AbstractPropertyParser& property_parser, bool require_end_semicolon)
 bool StyleSheetParser::ReadProperties(AbstractPropertyParser& property_parser, bool require_end_semicolon)
 {
 {
@@ -604,8 +623,7 @@ bool StyleSheetParser::ReadProperties(AbstractPropertyParser& property_parser, b
 	return true;
 	return true;
 }
 }
 
 
-// Updates the StyleNode tree, creating new nodes as necessary, setting the definition index
-bool StyleSheetParser::ImportProperties(StyleSheetNode* node, String rule_name, const PropertyDictionary& properties, int rule_specificity)
+StyleSheetNode* StyleSheetParser::ImportProperties(StyleSheetNode* node, String rule_name, const PropertyDictionary& properties, int rule_specificity)
 {
 {
 	StyleSheetNode* leaf_node = node;
 	StyleSheetNode* leaf_node = node;
 
 
@@ -694,7 +712,7 @@ bool StyleSheetParser::ImportProperties(StyleSheetNode* node, String rule_name,
 	// Merge the new properties with those already on the leaf node.
 	// Merge the new properties with those already on the leaf node.
 	leaf_node->ImportProperties(properties, rule_specificity);
 	leaf_node->ImportProperties(properties, rule_specificity);
 
 
-	return true;
+	return leaf_node;
 }
 }
 
 
 char StyleSheetParser::FindToken(String& buffer, const char* tokens, bool remove_token)
 char StyleSheetParser::FindToken(String& buffer, const char* tokens, bool remove_token)

+ 9 - 1
Source/Core/StyleSheetParser.h

@@ -39,6 +39,7 @@ class Stream;
 class StyleSheetNode;
 class StyleSheetNode;
 class AbstractPropertyParser;
 class AbstractPropertyParser;
 struct PropertySource;
 struct PropertySource;
+using StyleSheetNodeListRaw = std::vector<StyleSheetNode*>;
 
 
 /**
 /**
 	Helper class for parsing a style sheet into its memory representation.
 	Helper class for parsing a style sheet into its memory representation.
@@ -64,6 +65,12 @@ public:
 	/// @return True if the parse was successful, or false if an error occured.
 	/// @return True if the parse was successful, or false if an error occured.
 	bool ParseProperties(PropertyDictionary& parsed_properties, const String& properties);
 	bool ParseProperties(PropertyDictionary& parsed_properties, const String& properties);
 
 
+	// Converts a selector query to a tree of nodes.
+	// @param root_node Node to construct into.
+	// @param selectors The selector rules as a string value.
+	// @return The list of leaf nodes in the constructed tree, which are all owned by the root node.
+	static StyleSheetNodeListRaw ConstructNodes(StyleSheetNode& root_node, const String& selectors);
+
 private:
 private:
 	// Stream we're parsing from.
 	// Stream we're parsing from.
 	Stream* stream;
 	Stream* stream;
@@ -88,7 +95,8 @@ private:
 	// @param names The names of the nodes
 	// @param names The names of the nodes
 	// @param properties The dictionary of properties
 	// @param properties The dictionary of properties
 	// @param rule_specificity The specifity of the rule
 	// @param rule_specificity The specifity of the rule
-	bool ImportProperties(StyleSheetNode* node, String rule_name, const PropertyDictionary& properties, int rule_specificity);
+	// @return The leaf node of the rule
+	static StyleSheetNode* ImportProperties(StyleSheetNode* node, String rule_name, const PropertyDictionary& properties, int rule_specificity);
 
 
 	// Attempts to parse a @keyframes block
 	// Attempts to parse a @keyframes block
 	bool ParseKeyframeBlock(KeyframesMap & keyframes_map, const String & identifier, const String & rules, const PropertyDictionary & properties);
 	bool ParseKeyframeBlock(KeyframesMap & keyframes_map, const String & identifier, const String & rules, const PropertyDictionary & properties);

+ 2 - 2
changelog.md

@@ -57,9 +57,9 @@ For now, this is considered an experimental feature.
 - Have a look at the 'databinding' sample for usage examples.
 - Have a look at the 'databinding' sample for usage examples.
 - See discussion in [#83](https://github.com/mikke89/RmlUi/pull/83) and [#25](https://github.com/mikke89/RmlUi/issues/25).
 - See discussion in [#83](https://github.com/mikke89/RmlUi/pull/83) and [#25](https://github.com/mikke89/RmlUi/issues/25).
 
 
+### Other features and improvements
 
 
-### Other changes
-
+- Implemented `Element::QuerySelector` and `Element::QuerySelectorAll`.
 - Improved the SFML2 sample [#106](https://github.com/mikke89/RmlUi/pull/106) and [#103](https://github.com/mikke89/RmlUi/issues/103) (thanks @hachmeister).
 - Improved the SFML2 sample [#106](https://github.com/mikke89/RmlUi/pull/106) and [#103](https://github.com/mikke89/RmlUi/issues/103) (thanks @hachmeister).