// Ryzom - MMORPG Framework
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#include "stdpch.h"
#include
#include "nel/misc/types_nl.h"
#include "nel/gui/css_selector.h"
#include "nel/gui/html_element.h"
using namespace NLMISC;
#ifdef DEBUG_NEW
#define new DEBUG_NEW
#endif
namespace NLGUI
{
CCssSelector::CCssSelector(std::string elm, std::string id, std::string cls, char comb)
: Element(elm), Id(id), Class(), Attr(), PseudoClass(), Combinator(comb)
{
if (!cls.empty())
{
setClass(cls);
}
}
uint32 CCssSelector::specificity() const
{
uint ret = 0;
if (!Element.empty() && Element != "*") ret += 0x000001;
// Pseudo Element is added in CCssStyle
//if (!PseudoElement.empty()) ret += 0x000001;
if (!Class.empty()) ret += 0x000100 * Class.size();
if (!Attr.empty()) ret += 0x000100 * Attr.size();
// TODO: has different cases
if (!PseudoClass.empty()) ret += 0x000100 * PseudoClass.size();
if (!Id.empty()) ret += 0x010000;
return ret;
}
void CCssSelector::setClass(const std::string &cls)
{
std::vector parts;
NLMISC::splitString(toLower(cls), ".", parts);
for(uint i = 0; i< parts.size(); i++)
{
std::string cname = trim(parts[i]);
if (!cname.empty())
{
Class.push_back(cname);
}
}
}
void CCssSelector::addAttribute(const std::string &key, const std::string &val, char op)
{
Attr.push_back(SAttribute(key, val, op));
}
void CCssSelector::addPseudoClass(const std::string &key)
{
if (key.empty()) return;
PseudoClass.push_back(key);
}
bool CCssSelector::match(const CHtmlElement &elm) const
{
if (!Element.empty() && Element != "*" && Element != elm.Value)
{
return false;
}
if (!Id.empty() && Id != elm.getAttribute("id"))
{
return false;
}
if (!Class.empty() && !matchClass(elm))
{
return false;
}
if (!Attr.empty() && !matchAttributes(elm))
{
return false;
}
if (!PseudoClass.empty() && !matchPseudoClass(elm))
{
return false;
}
return true;
}
bool CCssSelector::matchClass(const CHtmlElement &elm) const
{
// make sure all class names we have, other has as well
for(uint i = 0; i< Class.size(); ++i)
{
if (!elm.hasClass(Class[i]))
{
return false;
}
}
return true;
}
bool CCssSelector::matchAttributes(const CHtmlElement &elm) const
{
// TODO: refactor into matchAttributeSelector
for(uint i = 0; i< Attr.size(); ++i)
{
if (!elm.hasAttribute(Attr[i].key)) return false;
std::string value = elm.getAttribute(Attr[i].key);
switch(Attr[i].op)
{
case '=':
if (Attr[i].value != value) return false;
break;
case '~':
{
// exact match to any of whitespace separated words from element attribute
if (Attr[i].value.empty()) return false;
std::vector parts;
NLMISC::splitString(value, " ", parts);
bool found = false;
for(uint j = 0; j < parts.size(); j++)
{
if (Attr[i].value == parts[j])
{
found = true;
break;
}
}
if (!found) return false;
}
break;
case '|':
// exact value, or start with val+'-'
if (value != Attr[i].value && value.find(Attr[i].value + "-") == std::string::npos) return false;
break;
case '^':
// prefix, starts with
if (Attr[i].value.empty()) return false;
if (value.find(Attr[i].value) != 0) return false;
break;
case '$':
// suffic, ends with
if (Attr[i].value.empty() || value.size() < Attr[i].value.size()) return false;
if (Attr[i].value == value.substr(value.size() - Attr[i].value.size())) return false;
break;
case '*':
if (Attr[i].value.empty()) return false;
if (value.find(Attr[i].value) == std::string::npos) return false;
break;
case ' ':
// contains key, ignore value
break;
default:
// unknown comparison
return false;
}
}
return true;
}
bool CCssSelector::matchPseudoClass(const CHtmlElement &elm) const
{
for(uint i = 0; i< PseudoClass.size(); i++)
{
if (PseudoClass[i] == "root")
{
// ':root' is just 'html' with higher specificity
if (elm.Value != "html") return false;
}
else if (PseudoClass[i] == "only-child")
{
if (elm.parent && !elm.parent->Children.empty()) return false;
}
else
{
if (PseudoClass[i] == "first-child")
{
if (elm.previousSibling) return false;
}
else if (PseudoClass[i] == "last-child")
{
if (elm.nextSibling) return false;
}
else if (PseudoClass[i].find("nth-child(") != std::string::npos)
{
sint a, b;
// TODO: there might be multiple :nth-child() on single selector, so current can't cache it
parseNth(PseudoClass[i], a, b);
// 1st child should be '1' and not '0'
if (!matchNth(elm.childIndex+1, a, b)) return false;
}
else
{
return false;
}
}
}
return true;
}
void CCssSelector::parseNth(const std::string &pseudo, sint &a, sint &b) const
{
a = 0;
b = 0;
std::string::size_type start = pseudo.find_first_of("(") + 1;
std::string::size_type end = pseudo.find_first_of(")");
if (start == std::string::npos) return;
std::string expr = toLower(pseudo.substr(start, end - start));
if (expr.empty()) return;
if (expr == "even")
{
// 2n+0
a = 2;
b = 0;
}
else if (expr == "odd")
{
// 2n+1
a = 2;
b = 1;
}
else
{
// -An+B, An+B, An-B
std::string::size_type pos;
start = 0;
pos = expr.find_first_of("n", start);
if (pos == std::string::npos)
{
fromString(expr, b);
}
else if (pos == 0)
{
// 'n' == '1n'
a = 1;
}
else if (expr[0] == '-' && pos == 1)
{
// '-n' == '-1n'
a = -1;
}
else
{
fromString(expr.substr(start, pos - start), a);
}
start = pos;
pos = expr.find_first_of("+-", start);
if (pos != std::string::npos && (expr[pos] == '+' || expr[pos] == '-'))
{
// copy with sign char
fromString(expr.substr(pos, end - pos), b);
}
}
}
bool CCssSelector::matchNth(sint childNr, sint a, sint b) const
{
if (a == 0)
{
return childNr == b;
}
else if (a > 0)
{
return childNr >= b && (childNr - b) % a == 0;
}
else
{
// a is negative from '-An+B'
return childNr <= b && (b - childNr) % (-a) == 0;
}
}
} // namespace