From 302870c268b5d7029ad4a1a537d7f611369f66c9 Mon Sep 17 00:00:00 2001 From: aquawicket Date: Thu, 30 Sep 2021 13:17:54 -0700 Subject: [PATCH 1/3] RCSS attribute selector support --- Source/Core/StyleSheet.cpp | 3 +- Source/Core/StyleSheetNode.cpp | 71 +++++++++++++++++++++++++++----- Source/Core/StyleSheetNode.h | 10 +++-- Source/Core/StyleSheetParser.cpp | 47 +++++++++++++++++++-- 4 files changed, 112 insertions(+), 19 deletions(-) diff --git a/Source/Core/StyleSheet.cpp b/Source/Core/StyleSheet.cpp index 9cf3f6afa..561f14b03 100644 --- a/Source/Core/StyleSheet.cpp +++ b/Source/Core/StyleSheet.cpp @@ -222,7 +222,8 @@ SharedPtr StyleSheet::GetElementDefinition(const Element* ele // trying to match nodes in the element's hierarchy to nodes in the style hierarchy. for (const StyleSheetNode* node : nodes) { - if (node->IsApplicable(element, true)) + //if (node->IsApplicable(element, true)) + if (node->IsApplicable(element, false)) { applicable_nodes.push_back(node); } diff --git a/Source/Core/StyleSheetNode.cpp b/Source/Core/StyleSheetNode.cpp index 74212b64d..d415f9332 100644 --- a/Source/Core/StyleSheetNode.cpp +++ b/Source/Core/StyleSheetNode.cpp @@ -40,14 +40,14 @@ StyleSheetNode::StyleSheetNode() CalculateAndSetSpecificity(); } -StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, const String& tag, const String& id, const StringList& classes, const StringList& pseudo_classes, const StructuralSelectorList& structural_selectors, bool child_combinator) - : parent(parent), tag(tag), id(id), class_names(classes), pseudo_class_names(pseudo_classes), structural_selectors(structural_selectors), child_combinator(child_combinator) +StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, const String& tag, const String& id, const StringList& classes, const ElementAttributes& attributes, const StringList& pseudo_classes, const StructuralSelectorList& structural_selectors, bool child_combinator) + : parent(parent), tag(tag), id(id), class_names(classes), attribute_names(attributes), pseudo_class_names(pseudo_classes), structural_selectors(structural_selectors), child_combinator(child_combinator) { CalculateAndSetSpecificity(); } -StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_selectors, bool child_combinator) - : parent(parent), tag(std::move(tag)), id(std::move(id)), class_names(std::move(classes)), pseudo_class_names(std::move(pseudo_classes)), structural_selectors(std::move(structural_selectors)), child_combinator(child_combinator) +StyleSheetNode::StyleSheetNode(StyleSheetNode* parent, String&& tag, String&& id, StringList&& classes, ElementAttributes&& attributes, StringList&& pseudo_classes, StructuralSelectorList&& structural_selectors, bool child_combinator) + : parent(parent), tag(std::move(tag)), id(std::move(id)), class_names(std::move(classes)), attribute_names(std::move(attributes)), pseudo_class_names(std::move(pseudo_classes)), structural_selectors(std::move(structural_selectors)), child_combinator(child_combinator) { CalculateAndSetSpecificity(); } @@ -57,12 +57,12 @@ StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(const StyleSheetNode& other // See if we match the target child for (const auto& child : children) { - if (child->EqualRequirements(other.tag, other.id, other.class_names, other.pseudo_class_names, other.structural_selectors, other.child_combinator)) + if (child->EqualRequirements(other.tag, other.id, other.class_names, other.attribute_names, other.pseudo_class_names, other.structural_selectors, other.child_combinator)) return child.get(); } // We don't, so create a new child - auto child = MakeUnique(this, other.tag, other.id, other.class_names, other.pseudo_class_names, other.structural_selectors, other.child_combinator); + auto child = MakeUnique(this, other.tag, other.id, other.class_names, other.attribute_names, other.pseudo_class_names, other.structural_selectors, other.child_combinator); StyleSheetNode* result = child.get(); children.push_back(std::move(child)); @@ -70,17 +70,17 @@ StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(const StyleSheetNode& other return result; } -StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_pseudo_classes, bool child_combinator) +StyleSheetNode* StyleSheetNode::GetOrCreateChildNode(String&& tag, String&& id, StringList&& classes, ElementAttributes&& attributes, StringList&& pseudo_classes, StructuralSelectorList&& structural_pseudo_classes, bool child_combinator) { // See if we match an existing child for (const auto& child : children) { - if (child->EqualRequirements(tag, id, classes, pseudo_classes, structural_pseudo_classes, child_combinator)) + if (child->EqualRequirements(tag, id, classes, attributes, pseudo_classes, structural_pseudo_classes, child_combinator)) return child.get(); } // We don't, so create a new child - auto child = MakeUnique(this, std::move(tag), std::move(id), std::move(classes), std::move(pseudo_classes), std::move(structural_pseudo_classes), child_combinator); + auto child = MakeUnique(this, std::move(tag), std::move(id), std::move(classes), std::move(attributes), std::move(pseudo_classes), std::move(structural_pseudo_classes), child_combinator); StyleSheetNode* result = child.get(); children.push_back(std::move(child)); @@ -107,7 +107,7 @@ UniquePtr StyleSheetNode::DeepCopy(StyleSheetNode* in_parent) co { RMLUI_ZoneScoped; - auto node = MakeUnique(in_parent, tag, id, class_names, pseudo_class_names, structural_selectors, child_combinator); + auto node = MakeUnique(in_parent, tag, id, class_names, attribute_names, pseudo_class_names, structural_selectors, child_combinator); node->properties = properties; node->children.resize(children.size()); @@ -158,12 +158,14 @@ bool StyleSheetNode::SetStructurallyVolatileRecursive(bool ancestor_is_structura return (self_is_structural_pseudo_class || descendant_is_structural_pseudo_class); } -bool StyleSheetNode::EqualRequirements(const String& _tag, const String& _id, const StringList& _class_names, const StringList& _pseudo_class_names, const StructuralSelectorList& _structural_selectors, bool _child_combinator) const +bool StyleSheetNode::EqualRequirements(const String& _tag, const String& _id, const StringList& _class_names, const ElementAttributes& _attribute_names, const StringList& _pseudo_class_names, const StructuralSelectorList& _structural_selectors, bool _child_combinator) const { if (tag != _tag) return false; if (id != _id) return false; + if (attribute_names != _attribute_names) + return false; if (class_names != _class_names) return false; if (pseudo_class_names != _pseudo_class_names) @@ -203,6 +205,9 @@ inline bool StyleSheetNode::Match(const Element* element) const if (!id.empty() && id != element->GetId()) return false; + if (!MatchAttributes(element)) + return false; + if (!MatchClassPseudoClass(element)) return false; @@ -240,6 +245,49 @@ inline bool StyleSheetNode::MatchStructuralSelector(const Element* element) cons return true; } +inline bool StyleSheetNode::MatchAttributes(const Element* element) const +{ + for(auto& attribute : attribute_names) + { + if(!element->HasAttribute(attribute.first)) + return false; + String attribute_value = element->GetAttribute(attribute.first, ""); + const String css_value = attribute.second.Get(); + if(css_value.size() > 2){ + String css_attribute_value = css_value.substr(2); + switch(css_value[0]){ + case '=': + if(attribute_value != css_attribute_value) + return false; + break; + case '~': + if(attribute_value.find(css_attribute_value) == String::npos) + return false; + break; + case '|': + if((attribute_value != css_attribute_value) && (attribute_value.find(css_attribute_value+"-") == String::npos)) + return false; + break; + case '^': + if(attribute_value.find(css_attribute_value) != 0) + return false; + break; + case '$': + if(attribute_value.find(css_attribute_value) != (attribute_value.size() - css_attribute_value.size())) + return false; + break; + case '*': + if(attribute_value.find(css_attribute_value) == String::npos) + return false; + break; + default: + break; + } + } + } + return true; +} + // 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, bool skip_id_tag) const { @@ -304,6 +352,7 @@ void StyleSheetNode::CalculateAndSetSpecificity() if (!id.empty()) specificity += 1'000'000; + specificity += 100'000*(int)attribute_names.size(); specificity += 100'000*(int)class_names.size(); specificity += 100'000*(int)pseudo_class_names.size(); specificity += 100'000*(int)structural_selectors.size(); diff --git a/Source/Core/StyleSheetNode.h b/Source/Core/StyleSheetNode.h index 3d77f7d61..927963a01 100644 --- a/Source/Core/StyleSheetNode.h +++ b/Source/Core/StyleSheetNode.h @@ -61,11 +61,11 @@ class StyleSheetNode { public: StyleSheetNode(); - StyleSheetNode(StyleSheetNode* parent, const String& tag, const String& id, const StringList& classes, const StringList& pseudo_classes, const StructuralSelectorList& structural_selectors, bool child_combinator); - StyleSheetNode(StyleSheetNode* parent, String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_selectors, bool child_combinator); + StyleSheetNode(StyleSheetNode* parent, const String& tag, const String& id, const StringList& classes, const ElementAttributes& attributes, const StringList& pseudo_classes, const StructuralSelectorList& structural_selectors, bool child_combinator); + StyleSheetNode(StyleSheetNode* parent, String&& tag, String&& id, StringList&& classes, ElementAttributes&& attributes, StringList&& pseudo_classes, StructuralSelectorList&& structural_selectors, bool child_combinator); /// Retrieves a child node with the given requirements if they match an existing node, or else creates a new one. - StyleSheetNode* GetOrCreateChildNode(String&& tag, String&& id, StringList&& classes, StringList&& pseudo_classes, StructuralSelectorList&& structural_selectors, bool child_combinator); + StyleSheetNode* GetOrCreateChildNode(String&& tag, String&& id, StringList&& classes, ElementAttributes&& attributes, StringList&& pseudo_classes, StructuralSelectorList&& structural_selectors, bool child_combinator); /// Retrieves or creates a child node with requirements equivalent to the 'other' node. StyleSheetNode* GetOrCreateChildNode(const StyleSheetNode& other); @@ -99,7 +99,7 @@ class StyleSheetNode private: // Returns true if the requirements of this node equals the given arguments. - bool EqualRequirements(const String& tag, const String& id, const StringList& classes, const StringList& pseudo_classes, const StructuralSelectorList& structural_pseudo_classes, bool child_combinator) const; + bool EqualRequirements(const String& tag, const String& id, const StringList& classes, const ElementAttributes& attributes, const StringList& pseudo_classes, const StructuralSelectorList& structural_pseudo_classes, bool child_combinator) const; void CalculateAndSetSpecificity(); @@ -107,6 +107,7 @@ class StyleSheetNode inline bool Match(const Element* element) const; inline bool MatchClassPseudoClass(const Element* element) const; inline bool MatchStructuralSelector(const Element* element) const; + inline bool MatchAttributes(const Element* element) const; // The parent of this node; is nullptr for the root node. StyleSheetNode* parent = nullptr; @@ -114,6 +115,7 @@ class StyleSheetNode // Node requirements String tag; String id; + ElementAttributes attribute_names; StringList class_names; StringList pseudo_class_names; StructuralSelectorList structural_selectors; // Represents structural pseudo classes diff --git a/Source/Core/StyleSheetParser.cpp b/Source/Core/StyleSheetParser.cpp index 26a83ca77..d8aa8daf5 100644 --- a/Source/Core/StyleSheetParser.cpp +++ b/Source/Core/StyleSheetParser.cpp @@ -903,6 +903,7 @@ StyleSheetNode* StyleSheetParser::ImportProperties(StyleSheetNode* node, String String tag; String id; StringList classes; + ElementAttributes attributes; StringList pseudo_classes; StructuralSelectorList structural_pseudo_classes; bool child_combinator = false; @@ -918,7 +919,8 @@ StyleSheetNode* StyleSheetParser::ImportProperties(StyleSheetNode* node, String name[end_index] != '#' && name[end_index] != '.' && name[end_index] != ':' && - name[end_index] != '>') + name[end_index] != '>' && + name[end_index] != '[') end_index++; String identifier = name.substr(start_index, end_index - start_index); @@ -928,8 +930,46 @@ StyleSheetNode* StyleSheetParser::ImportProperties(StyleSheetNode* node, String { case '#': id = identifier.substr(1); break; case '.': classes.push_back(identifier.substr(1)); break; - case ':': + case '[': { + const size_t attribute_end = identifier.find(']'); + if(attribute_end != String::npos) + { + String attribute_str = identifier.substr(1, attribute_end - 1); + attribute_str = StringUtilities::Replace(attribute_str, "\"", ""); + + if(!attribute_str.empty()) + { + const size_t equals_index = attribute_str.find('='); + if(equals_index != String::npos) + { + String attribute_name; + String attribute_value = attribute_str.substr(equals_index-1); + switch(attribute_value[0]) + { + case '~': + case '|': + case '^': + case '$': + case '*': + attribute_name = attribute_str.substr(0, equals_index-1); + break; + default: + attribute_value[0] = '='; + attribute_name = attribute_str.substr(0, equals_index); + break; + } + attributes[attribute_name] = attribute_value; + break; + } + } + attributes[attribute_str] = ""; + break; + } + } + break; + case ':': + { String pseudo_class_name = identifier.substr(1); StructuralSelector node_selector = StyleSheetFactory::GetSelector(pseudo_class_name); if (node_selector.selector) @@ -949,11 +989,12 @@ StyleSheetNode* StyleSheetParser::ImportProperties(StyleSheetNode* node, String // Sort the classes and pseudo-classes so they are consistent across equivalent declarations that shuffle the order around. std::sort(classes.begin(), classes.end()); + //std::sort(attributes.begin(), attributes.end()); std::sort(pseudo_classes.begin(), pseudo_classes.end()); std::sort(structural_pseudo_classes.begin(), structural_pseudo_classes.end()); // Get the named child node. - leaf_node = leaf_node->GetOrCreateChildNode(std::move(tag), std::move(id), std::move(classes), std::move(pseudo_classes), std::move(structural_pseudo_classes), child_combinator); + leaf_node = leaf_node->GetOrCreateChildNode(std::move(tag), std::move(id), std::move(classes), std::move(attributes), std::move(pseudo_classes), std::move(structural_pseudo_classes), child_combinator); } // Merge the new properties with those already on the leaf node. From 88ecf7daecdea6bcfd7acafda7979045569554df Mon Sep 17 00:00:00 2001 From: aquawicket Date: Thu, 30 Sep 2021 14:03:01 -0700 Subject: [PATCH 2/3] fix --- Source/Core/StyleSheetParser.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Source/Core/StyleSheetParser.cpp b/Source/Core/StyleSheetParser.cpp index d8aa8daf5..229e1f5e9 100644 --- a/Source/Core/StyleSheetParser.cpp +++ b/Source/Core/StyleSheetParser.cpp @@ -933,12 +933,11 @@ StyleSheetNode* StyleSheetParser::ImportProperties(StyleSheetNode* node, String case '[': { const size_t attribute_end = identifier.find(']'); - if(attribute_end != String::npos) - { + if(attribute_end != String::npos){ String attribute_str = identifier.substr(1, attribute_end - 1); attribute_str = StringUtilities::Replace(attribute_str, "\"", ""); - if(!attribute_str.empty()) + if(!attribute_str.empty()) //has attribute selector data { const size_t equals_index = attribute_str.find('='); if(equals_index != String::npos) @@ -948,15 +947,15 @@ StyleSheetNode* StyleSheetParser::ImportProperties(StyleSheetNode* node, String switch(attribute_value[0]) { case '~': - case '|': - case '^': - case '$': + case '|': + case '^': + case '$': case '*': - attribute_name = attribute_str.substr(0, equals_index-1); + attribute_name = attribute_str.substr(0, equals_index -1); break; default: - attribute_value[0] = '='; attribute_name = attribute_str.substr(0, equals_index); + attribute_value[0] = '='; break; } attributes[attribute_name] = attribute_value; From f40730633c721b0da6f3620721998f32e3918423 Mon Sep 17 00:00:00 2001 From: aquawicket Date: Fri, 22 Oct 2021 18:15:18 -0700 Subject: [PATCH 3/3] Update StyleSheetParser.cpp striping ' as well as " characters to support javascript --- Source/Core/StyleSheetParser.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/Core/StyleSheetParser.cpp b/Source/Core/StyleSheetParser.cpp index 229e1f5e9..6d45f74a5 100644 --- a/Source/Core/StyleSheetParser.cpp +++ b/Source/Core/StyleSheetParser.cpp @@ -936,6 +936,7 @@ StyleSheetNode* StyleSheetParser::ImportProperties(StyleSheetNode* node, String if(attribute_end != String::npos){ String attribute_str = identifier.substr(1, attribute_end - 1); attribute_str = StringUtilities::Replace(attribute_str, "\"", ""); + attribute_str = StringUtilities::Replace(attribute_str, "'", ""); if(!attribute_str.empty()) //has attribute selector data {