Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RCSS attribute selector support #240

Closed
5 changes: 4 additions & 1 deletion Source/Core/StyleSheet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,13 @@ SharedPtr<const ElementDefinition> StyleSheet::GetElementDefinition(const Elemen

for (const StyleSheetNode* node : nodes)
{

// We found a node that has at least one requirement matching the element. Now see if we satisfy the remaining requirements of the
// node, including all ancestor nodes. What this involves is traversing the style nodes backwards, trying to match nodes in the
// element's hierarchy to nodes in the style hierarchy.
if (node->IsApplicable(element))
//if (node->IsApplicable(element, true))
if (node->IsApplicable(element, false))
{
applicable_nodes.push_back(node);
}
}
Expand Down
71 changes: 60 additions & 11 deletions Source/Core/StyleSheetNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,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();
}
Expand All @@ -58,30 +58,30 @@ 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<StyleSheetNode>(this, other.tag, other.id, other.class_names, other.pseudo_class_names, other.structural_selectors, other.child_combinator);
auto child = MakeUnique<StyleSheetNode>(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));

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<StyleSheetNode>(this, std::move(tag), std::move(id), std::move(classes), std::move(pseudo_classes), std::move(structural_pseudo_classes), child_combinator);
auto child = MakeUnique<StyleSheetNode>(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));
Expand All @@ -108,7 +108,7 @@ UniquePtr<StyleSheetNode> StyleSheetNode::DeepCopy(StyleSheetNode* in_parent) co
{
RMLUI_ZoneScoped;

auto node = MakeUnique<StyleSheetNode>(in_parent, tag, id, class_names, pseudo_class_names, structural_selectors, child_combinator);
auto node = MakeUnique<StyleSheetNode>(in_parent, tag, id, class_names, attribute_names, pseudo_class_names, structural_selectors, child_combinator);

node->properties = properties;
node->children.resize(children.size());
Expand Down Expand Up @@ -178,12 +178,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)
Expand Down Expand Up @@ -223,6 +225,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;

Expand Down Expand Up @@ -260,6 +265,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<String>(attribute.first, "");
const String css_value = attribute.second.Get<String>();
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) const
{
Expand Down Expand Up @@ -329,6 +377,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();
Expand Down
10 changes: 6 additions & 4 deletions Source/Core/StyleSheetNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,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);

Expand Down Expand Up @@ -100,21 +100,23 @@ 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();

// Match an element to the local node requirements.
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;

// Node requirements
String tag;
String id;
ElementAttributes attribute_names;
StringList class_names;
StringList pseudo_class_names;
StructuralSelectorList structural_selectors; // Represents structural pseudo classes
Expand Down
47 changes: 44 additions & 3 deletions Source/Core/StyleSheetParser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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, "\"", "");
attribute_str = StringUtilities::Replace(attribute_str, "'", "");

if(!attribute_str.empty()) //has attribute selector data
{
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_name = attribute_str.substr(0, equals_index);
attribute_value[0] = '=';
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)
Expand All @@ -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.
Expand Down