commit
a76308cbde
@ -0,0 +1,150 @@
|
||||
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef CL_CSS_PARSER_H
|
||||
#define CL_CSS_PARSER_H
|
||||
|
||||
#include "nel/misc/types_nl.h"
|
||||
#include "nel/gui/css_style.h"
|
||||
#include "nel/gui/css_selector.h"
|
||||
|
||||
namespace NLGUI
|
||||
{
|
||||
/**
|
||||
* \brief CSS style parsing
|
||||
* \date 2019-03-15 10:50 GMT
|
||||
* \author Meelis Mägi (Nimetu)
|
||||
*/
|
||||
class CCssParser {
|
||||
public:
|
||||
// parse style declaration, eg "color: red; font-size: 10px;"
|
||||
static TStyle parseDecls(const std::string &styleString);
|
||||
|
||||
// parse css stylesheet
|
||||
void parseStylesheet(const std::string &cssString, std::vector<CCssStyle::SStyleRule> &rules);
|
||||
|
||||
private:
|
||||
// stylesheet currently parsed
|
||||
ucstring _Style;
|
||||
// keep track of current position in _Style
|
||||
size_t _Position;
|
||||
|
||||
std::vector<CCssStyle::SStyleRule> _Rules;
|
||||
|
||||
private:
|
||||
// @media ( .. ) { .. }
|
||||
void readAtRule();
|
||||
|
||||
// a#id.class[attr=val] { .. }
|
||||
void readRule();
|
||||
|
||||
// move past whitespace
|
||||
void skipWhitespace();
|
||||
|
||||
// skip valid IDENT
|
||||
bool skipIdentifier();
|
||||
|
||||
// skip over {}, (), or [] block
|
||||
void skipBlock();
|
||||
|
||||
// skip over string quoted with ' or "
|
||||
void skipString();
|
||||
|
||||
// backslash escape
|
||||
void escape();
|
||||
|
||||
// normalize newline chars and remove comments
|
||||
void preprocess();
|
||||
|
||||
// parse selectors + combinators
|
||||
std::vector<CCssSelector> parse_selector(const ucstring &sel, std::string &pseudoElement) const;
|
||||
|
||||
// parse selector and style
|
||||
void parseRule(const ucstring &selectorString, const ucstring &styleString);
|
||||
|
||||
inline bool is_eof() const
|
||||
{
|
||||
return _Position >= _Style.size();
|
||||
}
|
||||
|
||||
inline bool is_whitespace(ucchar ch) const
|
||||
{
|
||||
return (ch == (ucchar)' ' || ch == (ucchar)'\t' || ch == (ucchar)'\n');
|
||||
}
|
||||
|
||||
inline bool is_hex(ucchar ch) const
|
||||
{
|
||||
return ((ch >= (ucchar)'0' && ch <= (ucchar)'9') ||
|
||||
(ch >= (ucchar)'a' && ch <= (ucchar)'f') ||
|
||||
(ch >= (ucchar)'A' && ch <= (ucchar)'F'));
|
||||
}
|
||||
|
||||
inline bool maybe_escape() const
|
||||
{
|
||||
// escaping newline (\n) only allowed inside strings
|
||||
return (_Style.size() - _Position) >= 1 && _Style[_Position] == (ucchar)'\\' && _Style[_Position+1] != '\n';
|
||||
}
|
||||
|
||||
inline bool is_quote(ucchar ch) const
|
||||
{
|
||||
return ch== (ucchar)'"' || ch == (ucchar)'\'';
|
||||
}
|
||||
|
||||
inline bool is_block_open(ucchar ch) const
|
||||
{
|
||||
return ch == (ucchar)'{' || ch == (ucchar)'[' || ch == (ucchar)'(';
|
||||
}
|
||||
|
||||
inline bool is_block_close(ucchar ch, ucchar open) const
|
||||
{
|
||||
return ((open == '{' && ch == (ucchar)'}') ||
|
||||
(open == '[' && ch == (ucchar)']') ||
|
||||
(open == '(' && ch == (ucchar)')'));
|
||||
}
|
||||
|
||||
inline bool is_comment_open() const
|
||||
{
|
||||
if (_Position+1 > _Style.size())
|
||||
return false;
|
||||
|
||||
return _Style[_Position] == (ucchar)'/' && _Style[_Position+1] == (ucchar)'*';
|
||||
}
|
||||
|
||||
inline bool is_nonascii(ucchar ch) const
|
||||
{
|
||||
return ch >= 0x80 /*&& ch <= 255*/;
|
||||
}
|
||||
|
||||
inline bool is_alpha(ucchar ch) const
|
||||
{
|
||||
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z');
|
||||
}
|
||||
|
||||
inline bool is_digit(ucchar ch) const
|
||||
{
|
||||
return ch >= '0' && ch <= '9';
|
||||
}
|
||||
|
||||
inline bool is_nmchar(ucchar ch) const
|
||||
{
|
||||
// checking escape here does not check if next char is '\n' or not
|
||||
return ch == '_' || ch == '-' || is_alpha(ch) || is_digit(ch) || is_nonascii(ch) || ch == '\\'/*is_escape(ch)*/;
|
||||
}
|
||||
};
|
||||
}//namespace
|
||||
|
||||
#endif // CL_CSS_PARSER_H
|
||||
|
@ -0,0 +1,107 @@
|
||||
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef CL_CSS_SELECTOR_H
|
||||
#define CL_CSS_SELECTOR_H
|
||||
|
||||
#include "nel/misc/types_nl.h"
|
||||
|
||||
namespace NLGUI
|
||||
{
|
||||
class CHtmlElement;
|
||||
|
||||
/**
|
||||
* \brief CSS selector
|
||||
* \date 2019-03-15 10:50 GMT
|
||||
* \author Meelis Mägi (Nimetu)
|
||||
*/
|
||||
class CCssSelector
|
||||
{
|
||||
public:
|
||||
enum ECombinator {
|
||||
NONE = 0,
|
||||
GENERAL_CHILD,
|
||||
ADJACENT_SIBLING,
|
||||
GENERAL_SIBLING,
|
||||
CHILD_OF
|
||||
};
|
||||
|
||||
struct SAttribute {
|
||||
std::string key;
|
||||
std::string value;
|
||||
char op; // =, ~, |, ^, $, *
|
||||
SAttribute(const std::string &k, const std::string &v, char o)
|
||||
:key(k),value(v),op(o)
|
||||
{}
|
||||
};
|
||||
|
||||
std::string Element;
|
||||
std::string Id;
|
||||
std::vector<std::string> Class;
|
||||
std::vector<SAttribute> Attr;
|
||||
std::vector<std::string> PseudoClass;
|
||||
|
||||
// css combinator or \0 missing (first element)
|
||||
char Combinator;
|
||||
|
||||
public:
|
||||
// TODO: rewrite for ECombinator enum
|
||||
CCssSelector(std::string elm="", std::string id="", std::string cls="", char comb = '\0');
|
||||
|
||||
// helper for sorting
|
||||
uint32 specificity() const;
|
||||
|
||||
// set classes used, eg 'class1 class2'
|
||||
void setClass(const std::string &cls);
|
||||
|
||||
// add attribute to selector
|
||||
// ' ' op means 'key exists, ignore value'
|
||||
void addAttribute(const std::string &key, const std::string &val = "", char op = ' ');
|
||||
|
||||
// add pseudo class to selector, eg 'first-child'
|
||||
void addPseudoClass(const std::string &key);
|
||||
|
||||
// true if no rules have been defined
|
||||
bool empty() const
|
||||
{
|
||||
return Element.empty() && Id.empty() && Class.empty() && Attr.empty() && PseudoClass.empty();
|
||||
}
|
||||
|
||||
// Test current selector to html DOM element
|
||||
// NOTE: Does not check combinator
|
||||
bool match(const CHtmlElement &elm) const;
|
||||
|
||||
private:
|
||||
bool matchClass(const CHtmlElement &elm) const;
|
||||
bool matchAttributes(const CHtmlElement &elm) const;
|
||||
bool matchPseudoClass(const CHtmlElement &elm) const;
|
||||
|
||||
// match An+B rule to child index (1 based)
|
||||
bool matchNth(sint childNr, sint a, sint b) const;
|
||||
|
||||
// parse nth-child string to 'a' and 'b' components
|
||||
// :nth-child(odd)
|
||||
// :nth-child(even)
|
||||
// :nth-child(An+B)
|
||||
// :nth-child(-An+b)
|
||||
void parseNth(const std::string &pseudo, sint &a, sint &b) const;
|
||||
|
||||
};
|
||||
|
||||
}//namespace
|
||||
|
||||
#endif // CL_CSS_SELECTOR_H
|
||||
|
@ -0,0 +1,221 @@
|
||||
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef CL_CSS_STYLE_H
|
||||
#define CL_CSS_STYLE_H
|
||||
|
||||
#include "nel/misc/types_nl.h"
|
||||
#include "nel/misc/rgba.h"
|
||||
#include "nel/gui/css_selector.h"
|
||||
|
||||
namespace NLGUI
|
||||
{
|
||||
class CHtmlElement;
|
||||
|
||||
typedef std::map<std::string, std::string> TStyle;
|
||||
|
||||
/**
|
||||
* \brief CSS style rules
|
||||
* \date 2019-03-15 10:50 GMT
|
||||
* \author Meelis Mägi (Nimetu)
|
||||
*/
|
||||
class CStyleParams
|
||||
{
|
||||
public:
|
||||
struct STextShadow
|
||||
{
|
||||
public:
|
||||
STextShadow(bool enabled = false, bool outline = false, sint32 x=1, sint32 y=1, NLMISC::CRGBA color=NLMISC::CRGBA::Black)
|
||||
: Enabled(enabled), Outline(outline), X(x), Y(y), Color(color)
|
||||
{ }
|
||||
|
||||
bool Enabled;
|
||||
bool Outline;
|
||||
sint32 X;
|
||||
sint32 Y;
|
||||
NLMISC::CRGBA Color;
|
||||
};
|
||||
public:
|
||||
CStyleParams () : FontFamily(""), TextColor(255,255,255,255), TextShadow()
|
||||
{
|
||||
FontSize=10;
|
||||
FontWeight=400;
|
||||
FontOblique=false;
|
||||
Underlined=false;
|
||||
StrikeThrough=false;
|
||||
GlobalColor=false;
|
||||
Width=-1;
|
||||
Height=-1;
|
||||
MaxWidth=-1;
|
||||
MaxHeight=-1;
|
||||
BorderWidth=1;
|
||||
BackgroundColor=NLMISC::CRGBA::Black;
|
||||
BackgroundColorOver=NLMISC::CRGBA::Black;
|
||||
}
|
||||
|
||||
bool hasStyle(const std::string &key) const
|
||||
{
|
||||
return StyleRules.find(key) != StyleRules.end();
|
||||
}
|
||||
|
||||
std::string getStyle(const std::string &key) const
|
||||
{
|
||||
TStyle::const_iterator it = StyleRules.find(key);
|
||||
return (it != StyleRules.end() ? it->second : "");
|
||||
}
|
||||
|
||||
public:
|
||||
uint FontSize;
|
||||
uint FontWeight;
|
||||
bool FontOblique;
|
||||
std::string FontFamily;
|
||||
NLMISC::CRGBA TextColor;
|
||||
STextShadow TextShadow;
|
||||
bool GlobalColor;
|
||||
bool Underlined;
|
||||
bool StrikeThrough;
|
||||
sint32 Width;
|
||||
sint32 Height;
|
||||
sint32 MaxWidth;
|
||||
sint32 MaxHeight;
|
||||
sint32 BorderWidth;
|
||||
NLMISC::CRGBA BackgroundColor;
|
||||
NLMISC::CRGBA BackgroundColorOver;
|
||||
|
||||
std::string WhiteSpace;
|
||||
std::string TextAlign;
|
||||
std::string VerticalAlign;
|
||||
|
||||
TStyle StyleRules;
|
||||
};
|
||||
|
||||
class CCssStyle {
|
||||
public:
|
||||
struct SStyleRule {
|
||||
std::vector<CCssSelector> Selector;
|
||||
TStyle Properties;
|
||||
|
||||
// pseudo element like ':before'
|
||||
std::string PseudoElement;
|
||||
|
||||
// returns selector specificity
|
||||
uint specificity() const;
|
||||
};
|
||||
|
||||
// 'browser' style, overwriten with '<html>'
|
||||
CStyleParams Root;
|
||||
|
||||
// current element style
|
||||
CStyleParams Current;
|
||||
|
||||
// known style rules sorted by specificity
|
||||
std::vector<SStyleRule> _StyleRules;
|
||||
|
||||
private:
|
||||
std::vector<CStyleParams> _StyleStack;
|
||||
|
||||
// test if str is one of "thin/medium/thick" and return its pixel value
|
||||
bool scanCssLength(const std::string& str, uint32 &px) const;
|
||||
|
||||
// read style attribute
|
||||
void getStyleParams(const std::string &styleString, CStyleParams &style, const CStyleParams ¤t) const;
|
||||
void getStyleParams(const TStyle &styleRules, CStyleParams &style, const CStyleParams ¤t) const;
|
||||
|
||||
// merge src into dest by overwriting key in dest
|
||||
void merge(TStyle &dst, const TStyle &src) const;
|
||||
|
||||
// match selector to dom path
|
||||
bool match(const std::vector<CCssSelector> &selector, const CHtmlElement &elm) const;
|
||||
|
||||
// parse 'background' into 'background-color', 'background-image', etc
|
||||
void parseBackgroundShorthand(const std::string &value, CStyleParams &style) const;
|
||||
|
||||
public:
|
||||
void reset();
|
||||
|
||||
// parse <style>..</style> tag or css file content
|
||||
void parseStylesheet(const std::string &styleString);
|
||||
|
||||
// set element style from matching css rules
|
||||
void getStyleFor(CHtmlElement &elm) const;
|
||||
|
||||
inline uint getFontSizeSmaller() const
|
||||
{
|
||||
if (Current.FontSize < 5)
|
||||
return 3;
|
||||
return Current.FontSize-2;
|
||||
}
|
||||
|
||||
sint styleStackIndex = 0;
|
||||
|
||||
inline void pushStyle()
|
||||
{
|
||||
styleStackIndex++;
|
||||
_StyleStack.push_back(Current);
|
||||
|
||||
Current.Width=-1;
|
||||
Current.Height=-1;
|
||||
Current.MaxWidth=-1;
|
||||
Current.MaxHeight=-1;
|
||||
Current.BorderWidth=1;
|
||||
|
||||
Current.StyleRules.clear();
|
||||
}
|
||||
|
||||
inline void popStyle()
|
||||
{
|
||||
styleStackIndex--;
|
||||
if (_StyleStack.empty())
|
||||
{
|
||||
Current = Root;
|
||||
}
|
||||
else
|
||||
{
|
||||
Current = _StyleStack.back();
|
||||
_StyleStack.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// apply style to this.Root
|
||||
void applyRootStyle(const std::string &styleString);
|
||||
void applyRootStyle(const TStyle &styleRules);
|
||||
|
||||
// apply style to this.Current
|
||||
void applyStyle(const std::string &styleString);
|
||||
void applyStyle(const TStyle &styleRules);
|
||||
|
||||
void applyCssMinMax(sint32 &width, sint32 &height, sint32 minw=0, sint32 minh=0, sint32 maxw=0, sint32 maxh=0) const;
|
||||
|
||||
// check if current style property matches value
|
||||
bool checkStyle(const std::string &key, const std::string &val) const
|
||||
{
|
||||
return Current.hasStyle(key) && Current.getStyle(key) == val;
|
||||
}
|
||||
|
||||
bool hasStyle(const std::string &key) const
|
||||
{
|
||||
return Current.hasStyle(key);
|
||||
}
|
||||
|
||||
std::string getStyle(const std::string &key) const
|
||||
{
|
||||
return Current.getStyle(key);
|
||||
}
|
||||
};
|
||||
}//namespace
|
||||
|
||||
#endif // CL_CSS_STYLE_H
|
||||
|
@ -0,0 +1,86 @@
|
||||
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef CL_HTML_ELEMENT_H
|
||||
#define CL_HTML_ELEMENT_H
|
||||
|
||||
#include "nel/misc/types_nl.h"
|
||||
#include "nel/gui/css_style.h"
|
||||
|
||||
namespace NLGUI
|
||||
{
|
||||
/**
|
||||
* \brief HTML element
|
||||
* \date 2019-04-25 18:23 GMT
|
||||
* \author Meelis Mägi (Nimetu)
|
||||
*/
|
||||
class CHtmlElement
|
||||
{
|
||||
public:
|
||||
enum ENodeType {
|
||||
NONE = 0,
|
||||
ELEMENT_NODE = 1,
|
||||
TEXT_NODE = 3,
|
||||
};
|
||||
|
||||
uint ID; // libwww element enum
|
||||
ENodeType Type;
|
||||
std::string Value; // text node value or element node name
|
||||
std::map<std::string, std::string> Attributes;
|
||||
std::list<CHtmlElement> Children;
|
||||
|
||||
// class names for css matching
|
||||
std::set<std::string> ClassNames;
|
||||
|
||||
// defined style and :before/:after pseudo elements
|
||||
TStyle Style;
|
||||
TStyle StyleBefore;
|
||||
TStyle StyleAfter;
|
||||
|
||||
// hierarchy
|
||||
CHtmlElement *parent;
|
||||
CHtmlElement *previousSibling;
|
||||
CHtmlElement *nextSibling;
|
||||
|
||||
// n'th ELEMENT_NODE in parent.Children, for :nth-child() rules
|
||||
uint childIndex;
|
||||
|
||||
CHtmlElement(ENodeType type = NONE, std::string value = "");
|
||||
|
||||
// returns true if rhs is same pointer
|
||||
friend bool operator==(const CHtmlElement &lhs, const CHtmlElement &rhs)
|
||||
{
|
||||
return &lhs == &rhs;
|
||||
}
|
||||
|
||||
bool hasAttribute(const std::string &key) const;
|
||||
|
||||
bool hasNonEmptyAttribute(const std::string &key) const;
|
||||
|
||||
std::string getAttribute(const std::string &key) const;
|
||||
|
||||
bool hasClass(const std::string &key) const;
|
||||
|
||||
// update Children index/parent/next/prevSibling pointers
|
||||
void reindexChilds();
|
||||
|
||||
// debug
|
||||
std::string toString(bool tree = false, uint depth = 0) const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -0,0 +1,52 @@
|
||||
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#ifndef CL_HTML_PARSER_H
|
||||
#define CL_HTML_PARSER_H
|
||||
|
||||
#include "nel/misc/types_nl.h"
|
||||
|
||||
namespace NLGUI
|
||||
{
|
||||
class CHtmlElement;
|
||||
|
||||
/**
|
||||
* \brief HTML parsing
|
||||
* \date 2019-03-15 10:50 GMT
|
||||
* \author Meelis Mägi (Nimetu)
|
||||
*/
|
||||
class CHtmlParser
|
||||
{
|
||||
public:
|
||||
bool parseHtml(std::string htmlString) const;
|
||||
|
||||
// parse html string into DOM, extract <style> tags into styleString, <link stylesheet> urls into links
|
||||
void getDOM(std::string htmlString, CHtmlElement &parent, std::string &styleString, std::vector<std::string> &links) const;
|
||||
|
||||
private:
|
||||
// iterate over libxml html tree, build DOM, and join all <style> tags together
|
||||
void parseNode(xmlNode *a_node, CHtmlElement &parent, std::string &styleString, std::vector<std::string> &links) const;
|
||||
|
||||
// read <style> tag and add its content to styleString
|
||||
void parseStyle(xmlNode *a_node, std::string &styleString) const;
|
||||
|
||||
// update parent/sibling in elm.Children
|
||||
void reindexChilds(CHtmlElement &elm) const;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@ -0,0 +1,716 @@
|
||||
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "stdpch.h"
|
||||
|
||||
#include <string>
|
||||
#include "nel/misc/types_nl.h"
|
||||
#include "nel/gui/css_parser.h"
|
||||
#include "nel/gui/css_style.h"
|
||||
#include "nel/gui/css_selector.h"
|
||||
|
||||
using namespace NLMISC;
|
||||
|
||||
#ifdef DEBUG_NEW
|
||||
#define new DEBUG_NEW
|
||||
#endif
|
||||
|
||||
namespace NLGUI
|
||||
{
|
||||
// ***************************************************************************
|
||||
// Parse style declarations style, eg. "color:red; font-size: 10px;"
|
||||
//
|
||||
// key is converted to lowercase
|
||||
// value is left as is
|
||||
TStyle CCssParser::parseDecls(const std::string &styleString)
|
||||
{
|
||||
TStyle styles;
|
||||
std::vector<std::string> elements;
|
||||
NLMISC::splitString(styleString, ";", elements);
|
||||
|
||||
for(uint i = 0; i < elements.size(); ++i)
|
||||
{
|
||||
std::string::size_type pos;
|
||||
pos = elements[i].find_first_of(':');
|
||||
if (pos != std::string::npos)
|
||||
{
|
||||
std::string key = trim(toLower(elements[i].substr(0, pos)));
|
||||
std::string value = trim(elements[i].substr(pos+1));
|
||||
styles[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return styles;
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// Parse stylesheet, eg content from main.css file
|
||||
//
|
||||
// Return all found rules
|
||||
void CCssParser::parseStylesheet(const std::string &cssString, std::vector<CCssStyle::SStyleRule> &result)
|
||||
{
|
||||
_Rules.clear();
|
||||
_Style.clear();
|
||||
|
||||
_Style.fromUtf8(cssString);
|
||||
preprocess();
|
||||
|
||||
_Position = 0;
|
||||
while(!is_eof())
|
||||
{
|
||||
skipWhitespace();
|
||||
|
||||
if (_Style[_Position] == (ucchar)'@')
|
||||
readAtRule();
|
||||
else
|
||||
readRule();
|
||||
}
|
||||
|
||||
result.insert(result.end(), _Rules.begin(), _Rules.end());
|
||||
_Rules.clear();
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// Parse selector with style string
|
||||
// selector: "a#id .class"
|
||||
// style: "color: red; font-size: 10px;"
|
||||
//
|
||||
// @internal
|
||||
void CCssParser::parseRule(const ucstring &selectorString, const ucstring &styleString)
|
||||
{
|
||||
std::vector<ucstring> selectors;
|
||||
NLMISC::explode(selectorString, ucstring(","), selectors);
|
||||
|
||||
TStyle props;
|
||||
props = parseDecls(styleString.toUtf8());
|
||||
|
||||
// duplicate props to each selector in selector list,
|
||||
// example 'div > p, h1' creates 'div>p' and 'h1'
|
||||
for(uint i=0; i<selectors.size(); ++i)
|
||||
{
|
||||
CCssStyle::SStyleRule rule;
|
||||
|
||||
rule.Selector = parse_selector(trim(selectors[i]), rule.PseudoElement);
|
||||
rule.Properties = props;
|
||||
|
||||
if (!rule.Selector.empty())
|
||||
{
|
||||
_Rules.push_back(rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// Skip over at-rule
|
||||
// @import ... ;
|
||||
// @charset ... ;
|
||||
// @media query { .. }
|
||||
//
|
||||
// @internal
|
||||
void CCssParser::readAtRule()
|
||||
{
|
||||
// skip '@'
|
||||
_Position++;
|
||||
|
||||
// skip 'import', 'media', etc
|
||||
skipIdentifier();
|
||||
|
||||
// skip at-rule statement
|
||||
while(!is_eof() && _Style[_Position] != (ucchar)';')
|
||||
{
|
||||
if (maybe_escape())
|
||||
{
|
||||
escape();
|
||||
}
|
||||
else if (is_quote(_Style[_Position]))
|
||||
{
|
||||
skipString();
|
||||
}
|
||||
else if (is_block_open(_Style[_Position]))
|
||||
{
|
||||
bool mustBreak = (_Style[_Position] == '{');
|
||||
skipBlock();
|
||||
|
||||
if(mustBreak)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_Position++;
|
||||
}
|
||||
}
|
||||
|
||||
// skip ';' or '}'
|
||||
_Position++;
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// skip over "elm#id.selector[attr]:peseudo, .sel2 { rule }" block
|
||||
// @internal
|
||||
void CCssParser::readRule()
|
||||
{
|
||||
size_t start;
|
||||
|
||||
// selector
|
||||
start = _Position;
|
||||
while(!is_eof())
|
||||
{
|
||||
if (maybe_escape())
|
||||
_Position++;
|
||||
else if (is_quote(_Style[_Position]))
|
||||
skipString();
|
||||
else if (_Style[_Position] == (ucchar)'[')
|
||||
skipBlock();
|
||||
else if (_Style[_Position] == (ucchar)'{')
|
||||
break;
|
||||
else
|
||||
_Position++;
|
||||
}
|
||||
|
||||
if (!is_eof())
|
||||
{
|
||||
ucstring selector;
|
||||
selector.append(_Style, start, _Position - start);
|
||||
|
||||
skipWhitespace();
|
||||
|
||||
// declaration block
|
||||
start = _Position;
|
||||
skipBlock();
|
||||
if (_Position <= _Style.size())
|
||||
{
|
||||
ucstring rules;
|
||||
rules.append(_Style, start + 1, _Position - start - 2);
|
||||
|
||||
parseRule(selector, rules);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// skip over \abcdef escaped sequence or escaped newline char
|
||||
// @internal
|
||||
void CCssParser::escape()
|
||||
{
|
||||
// skip '\'
|
||||
_Position++;
|
||||
if (is_hex(_Style[_Position]))
|
||||
{
|
||||
// TODO: '\abc def' should be considered one string
|
||||
for(uint i=0; i<6 && is_hex(_Style[_Position]); i++)
|
||||
_Position++;
|
||||
|
||||
if (_Style[_Position] == (ucchar)' ')
|
||||
_Position++;
|
||||
}
|
||||
else if (_Style[_Position] != 0x0A)
|
||||
_Position++;
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// @internal
|
||||
bool CCssParser::skipIdentifier()
|
||||
{
|
||||
size_t start = _Position;
|
||||
bool valid = true;
|
||||
while(!is_eof() && valid)
|
||||
{
|
||||
if (maybe_escape())
|
||||
{
|
||||
escape();
|
||||
continue;
|
||||
}
|
||||
else if (is_alpha(_Style[_Position]))
|
||||
{
|
||||
// valid
|
||||
}
|
||||
else if (is_digit(_Style[_Position]))
|
||||
{
|
||||
if (_Position == start)
|
||||
{
|
||||
// cannot start with digit
|
||||
valid = false;
|
||||
}
|
||||
else if ((_Position - start) == 0 && _Style[_Position-1] == (ucchar)'-')
|
||||
{
|
||||
// cannot start with -#
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
else if (_Style[_Position] == (ucchar)'_')
|
||||
{
|
||||
// valid
|
||||
}
|
||||
else if (_Style[_Position] >= 0x0080)
|
||||
{
|
||||
// valid
|
||||
}
|
||||
else if (_Style[_Position] == (ucchar)'-')
|
||||
{
|
||||
if ((_Position - start) == 1 && _Style[_Position-1] == (ucchar)'-')
|
||||
{
|
||||
// cannot start with --
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// we're done
|
||||
break;
|
||||
}
|
||||
|
||||
_Position++;
|
||||
}
|
||||
|
||||
return valid && !is_eof();
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// skip over (..), [..], or {..} blocks
|
||||
// @internal
|
||||
void CCssParser::skipBlock()
|
||||
{
|
||||
ucchar startChar = _Style[_Position];
|
||||
|
||||
// block start
|
||||
_Position++;
|
||||
while(!is_eof() && !is_block_close(_Style[_Position], startChar))
|
||||
{
|
||||
if (maybe_escape())
|
||||
// skip backslash and next char
|
||||
_Position += 2;
|
||||
else if (is_quote(_Style[_Position]))
|
||||
skipString();
|
||||
else if (is_block_open(_Style[_Position]))
|
||||
skipBlock();
|
||||
else
|
||||
_Position++;
|
||||
}
|
||||
|
||||
// block end
|
||||
_Position++;
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// skip over quoted string
|
||||
// @internal
|
||||
void CCssParser::skipString()
|
||||
{
|
||||
ucchar endChar = _Style[_Position];
|
||||
|
||||
// quote start
|
||||
_Position++;
|
||||
while(!is_eof() && _Style[_Position] != endChar)
|
||||
{
|
||||
if (maybe_escape())
|
||||
_Position++;
|
||||
|
||||
_Position++;
|
||||
}
|
||||
|
||||
// quote end
|
||||
_Position++;
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// @internal
|
||||
void CCssParser::skipWhitespace()
|
||||
{
|
||||
while(!is_eof() && is_whitespace(_Style[_Position]))
|
||||
_Position++;
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// parse selector list
|
||||
// @internal
|
||||
std::vector<CCssSelector> CCssParser::parse_selector(const ucstring &sel, std::string &pseudoElement) const
|
||||
{
|
||||
std::vector<CCssSelector> result;
|
||||
CCssSelector current;
|
||||
|
||||
pseudoElement.clear();
|
||||
|
||||
bool failed = false;
|
||||
ucstring::size_type start = 0, pos = 0;
|
||||
while(pos < sel.size())
|
||||
{
|
||||
ucstring uc;
|
||||
uc = sel[pos];
|
||||
if (is_nmchar(sel[pos]) && current.empty())
|
||||
{
|
||||
pos++;
|
||||
|
||||
while(pos < sel.size() && is_nmchar(sel[pos]))
|
||||
pos++;
|
||||
|
||||
current.Element = toLower(sel.substr(start, pos - start).toUtf8());
|
||||
start = pos;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(sel[pos] == '#')
|
||||
{
|
||||
pos++;
|
||||
start=pos;
|
||||
|
||||
while(pos < sel.size() && is_nmchar(sel[pos]))
|
||||
pos++;
|
||||
|
||||
current.Id = toLower(sel.substr(start, pos - start).toUtf8());
|
||||
start = pos;
|
||||
}
|
||||
else if (sel[pos] == '.')
|
||||
{
|
||||
pos++;
|
||||
start=pos;
|
||||
|
||||
// .classA.classB
|
||||
while(pos < sel.size() && (is_nmchar(sel[pos]) || sel[pos] == '.'))
|
||||
pos++;
|
||||
|
||||
current.setClass(toLower(sel.substr(start, pos - start).toUtf8()));
|
||||
start = pos;
|
||||
}
|
||||
else if (sel[pos] == '[')
|
||||
{
|
||||
pos++;
|
||||
start = pos;
|
||||
|
||||
if (is_whitespace(sel[pos]))
|
||||
{
|
||||
while(pos < sel.size() && is_whitespace(sel[pos]))
|
||||
pos++;
|
||||
|
||||
start = pos;
|
||||
}
|
||||
|
||||
ucstring key;
|
||||
ucstring value;
|
||||
ucchar op = ' ';
|
||||
|
||||
// key
|
||||
while(pos < sel.size() && is_nmchar(sel[pos]))
|
||||
pos++;
|
||||
|
||||
key = sel.substr(start, pos - start);
|
||||
if (pos == sel.size()) break;
|
||||
|
||||
if (is_whitespace(sel[pos]))
|
||||
{
|
||||
while(pos < sel.size() && is_whitespace(sel[pos]))
|
||||
pos++;
|
||||
|
||||
if (pos == sel.size()) break;
|
||||
}
|
||||
|
||||
if (sel[pos] == ']')
|
||||
{
|
||||
current.addAttribute(key.toUtf8());
|
||||
}
|
||||
else
|
||||
{
|
||||
// operand
|
||||
op = sel[pos];
|
||||
if (op == '~' || op == '|' || op == '^' || op == '$' || op == '*')
|
||||
{
|
||||
pos++;
|
||||
if (pos == sel.size()) break;
|
||||
}
|
||||
|
||||
// invalid rule?, eg [attr^value]
|
||||
if (sel[pos] != '=')
|
||||
{
|
||||
while(pos < sel.size() && sel[pos] != ']')
|
||||
pos++;
|
||||
|
||||
if (pos == sel.size()) break;
|
||||
|
||||
start = pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
// skip '='
|
||||
pos++;
|
||||
|
||||
if (is_whitespace(sel[pos]))
|
||||
{
|
||||
while(pos < sel.size() && is_whitespace(sel[pos]))
|
||||
pos++;
|
||||
|
||||
if (pos == sel.size()) break;
|
||||
}
|
||||
|
||||
// value
|
||||
start = pos;
|
||||
bool quote = false;
|
||||
char quoteOpen;
|
||||
while(pos < sel.size())
|
||||
{
|
||||
if (sel[pos] == '\'' || sel[pos] == '"')
|
||||
{
|
||||
// value is quoted
|
||||
start = pos;
|
||||
pos++;
|
||||
while(pos < sel.size() && sel[pos] != sel[start])
|
||||
{
|
||||
if (sel[pos] == '\\')
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
if (pos == sel.size()) break;
|
||||
|
||||
value = sel.substr(start + 1, pos - start - 1);
|
||||
break;
|
||||
}
|
||||
else if (sel[pos] == '\\')
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
else if (!quote && sel[pos] == ']')
|
||||
{
|
||||
// unquoted value
|
||||
value = sel.substr(start, pos - start);
|
||||
break;
|
||||
}
|
||||
|
||||
pos++;
|
||||
} // while 'value'
|
||||
|
||||
// TODO: scan for sel[pos] == ']'
|
||||
if (pos == sel.size()) break;
|
||||
// whitespace between quote and ], ie '[ attr $= "val" ]'
|
||||
if (sel[pos] != ']')
|
||||
{
|
||||
while(pos < sel.size() && sel[pos] != ']')
|
||||
pos++;
|
||||
}
|
||||
if (pos == sel.size()) break;
|
||||
|
||||
current.addAttribute(key.toUtf8(), value.toUtf8(), (char)op);
|
||||
} // op error
|
||||
} // no value
|
||||
|
||||
// skip ']'
|
||||
pos++;
|
||||
|
||||
start = pos;
|
||||
}
|
||||
else if (sel[pos] == ':')
|
||||
{
|
||||
pos++;
|
||||
start=pos;
|
||||
// pseudo element, eg '::before'
|
||||
if (pos < sel.size() && sel[pos] == ':')
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
// :first-child
|
||||
// :nth-child(2n+0)
|
||||
// :not(h1, div#main)
|
||||
// :not(:nth-child(2n+0))
|
||||
// has no support for quotes, eg :not(h1[attr=")"]) fails
|
||||
while(pos < sel.size() && (is_nmchar(sel[pos]) || sel[pos] == '('))
|
||||
{
|
||||
if (sel[pos] == '(')
|
||||
{
|
||||
uint open = 1;
|
||||
pos++;
|
||||
while(pos < sel.size() && open > 0)
|
||||
{
|
||||
if (sel[pos] == ')')
|
||||
open--;
|
||||
else if (sel[pos] == '(')
|
||||
open++;
|
||||
|
||||
pos++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
std::string key = toLower(sel.substr(start, pos - start).toUtf8());
|
||||
if (key.empty())
|
||||
{
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (key[0] == ':' || key == "after" || key == "before" || key == "cue" || key == "first-letter" || key == "first-line")
|
||||
{
|
||||
if (!pseudoElement.empty())
|
||||
{
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
if (key[0] != ':')
|
||||
{
|
||||
pseudoElement = ":" + key;
|
||||
}
|
||||
else
|
||||
{
|
||||
pseudoElement = key;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
current.addPseudoClass(key);
|
||||
}
|
||||
|
||||
start = pos;
|
||||
}
|
||||
else if (!current.empty())
|
||||
{
|
||||
// pseudo element like ':before' can only be set on the last selector
|
||||
// user action pseudo classes can be used after pseudo element (ie, :focus, :hover)
|
||||
// there is no support for those and its safe to just fail the selector
|
||||
if (!result.empty() && !pseudoElement.empty())
|
||||
{
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// start new selector as combinator is part of next selector
|
||||
result.push_back(current);
|
||||
current = CCssSelector();
|
||||
|
||||
// detect and remove whitespace around combinator, eg ' > '
|
||||
bool isSpace = is_whitespace(sel[pos]);;
|
||||
while(pos < sel.size() && is_whitespace(sel[pos]))
|
||||
pos++;
|
||||
|
||||
if (sel[pos] == '>' || sel[pos] == '+' || sel[pos] == '~')
|
||||
{
|
||||
current.Combinator = sel[pos];
|
||||
pos++;
|
||||
|
||||
while(pos < sel.size() && is_whitespace(sel[pos]))
|
||||
pos++;
|
||||
}
|
||||
else if (isSpace)
|
||||
{
|
||||
current.Combinator = ' ';
|
||||
}
|
||||
else
|
||||
{
|
||||
// unknown
|
||||
current.Combinator = sel[pos];
|
||||
pos++;
|
||||
}
|
||||
|
||||
start = pos;
|
||||
}
|
||||
else
|
||||
{
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
if (failed)
|
||||
{
|
||||
result.clear();
|
||||
}
|
||||
else if (result.empty() || !current.empty())
|
||||
{
|
||||
// pseudo element like ':before' can only be set on the last selector
|
||||
if (!result.empty() && !pseudoElement.empty())
|
||||
{
|
||||
// failed
|
||||
result.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
result.push_back(current);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
// @internal
|
||||
void CCssParser::preprocess()
|
||||
{
|
||||
_Position = 0;
|
||||
|
||||
size_t start;
|
||||
size_t charsLeft;
|
||||
bool quote = false;
|
||||
ucchar quoteChar;
|
||||
while(!is_eof())
|
||||
{
|
||||
charsLeft = _Style.size() - _Position - 1;
|
||||
|
||||
// FF, CR
|
||||
if (_Style[_Position] == 0x0C || _Style[_Position] == 0x0D)
|
||||
{
|
||||
uint len = 1;
|
||||
// CR, LF
|
||||
if (charsLeft >= 1 && _Style[_Position] == 0x0D && _Style[_Position+1] == 0x0A)
|
||||
len++;
|
||||
|
||||
ucstring tmp;
|
||||
tmp += 0x000A;
|
||||
_Style.replace(_Position, 1, tmp);
|
||||
}
|
||||
else if (_Style[_Position] == 0x00)
|
||||
{
|
||||
// Unicode replacement character
|
||||
_Style[_Position] = 0xFFFD;
|
||||
}
|
||||
else
|
||||
{
|
||||
// strip comments for easier parsing
|
||||
if (_Style[_Position] == '\\')
|
||||
{
|
||||
_Position++;
|
||||
}
|
||||
else if (is_quote(_Style[_Position]))
|
||||
{
|
||||
if (!quote)
|
||||
quoteChar = _Style[_Position];
|
||||
|
||||
if (quote && _Style[_Position] == quoteChar)
|
||||
quote = !quote;
|
||||
}
|
||||
else if (!quote && is_comment_open())
|
||||
{
|
||||
size_t pos = _Style.find(ucstring("*/"), _Position + 2);
|
||||
if (pos == std::string::npos)
|
||||
pos = _Style.size();
|
||||
|
||||
_Style.erase(_Position, pos - _Position + 2);
|
||||
ucstring uc;
|
||||
uc = _Style[_Position];
|
||||
|
||||
// _Position is already at correct place
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
_Position++;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
@ -0,0 +1,314 @@
|
||||
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "stdpch.h"
|
||||
|
||||
#include <string>
|
||||
#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<std::string> 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<std::string> 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
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,168 @@
|
||||
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
#include "stdpch.h"
|
||||
|
||||
#include "nel/gui/html_element.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace NLMISC;
|
||||
|
||||
#ifdef DEBUG_NEW
|
||||
#define new DEBUG_NEW
|
||||
#endif
|
||||
|
||||
namespace NLGUI
|
||||
{
|
||||
// ***************************************************************************
|
||||
CHtmlElement::CHtmlElement(ENodeType type, std::string value)
|
||||
: ID(0), Type(type), Value(value), parent(NULL), previousSibling(NULL), nextSibling(NULL), childIndex(0)
|
||||
{}
|
||||
|
||||
// ***************************************************************************
|
||||
bool CHtmlElement::hasAttribute(const std::string &key) const
|
||||
{
|
||||
return Attributes.find(key) != Attributes.end();
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
bool CHtmlElement::hasNonEmptyAttribute(const std::string &key) const
|
||||
{
|
||||
return !getAttribute(key).empty();
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
std::string CHtmlElement::getAttribute(const std::string &key) const
|
||||
{
|
||||
std::map<std::string, std::string>::const_iterator it = Attributes.find(key);
|
||||
return (it != Attributes.end() ? it->second : "");
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
bool CHtmlElement::hasClass(const std::string &key) const
|
||||
{
|
||||
return ClassNames.find(key) != ClassNames.end();
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
void CHtmlElement::reindexChilds()
|
||||
{
|
||||
uint index = 0;
|
||||
CHtmlElement *prev = NULL;
|
||||
std::list<CHtmlElement>::iterator it;
|
||||
for(it = Children.begin(); it != Children.end(); ++it)
|
||||
{
|
||||
if (it->Type == ELEMENT_NODE)
|
||||
{
|
||||
it->parent = this;
|
||||
it->previousSibling = prev;
|
||||
it->nextSibling = NULL;
|
||||
if (prev)
|
||||
{
|
||||
prev->nextSibling = &(*it);
|
||||
}
|
||||
it->childIndex = index;
|
||||
index++;
|
||||
prev = &(*it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ***************************************************************************
|
||||
std::string CHtmlElement::toString(bool tree, uint depth) const
|
||||
{
|
||||
std::string result;
|
||||
if (depth > 0)
|
||||
{
|
||||
result = NLMISC::toString("[%d]", depth);
|
||||
result.append(depth, '-');
|
||||
}
|
||||
if (Type == NONE || Type == ELEMENT_NODE)
|
||||
{
|
||||
result += "<" + Value;
|
||||
for(std::map<std::string, std::string>::const_iterator it = Attributes.begin(); it != Attributes.end(); ++it)
|
||||
{
|
||||
if (it->first == "class")
|
||||
{
|
||||
result += " class=\"";
|
||||
for(std::set<std::string>::const_iterator it2 = ClassNames.begin(); it2 != ClassNames.end(); ++it2)
|
||||
{
|
||||
if (it2 != ClassNames.begin())
|
||||
{
|
||||
result += " ";
|
||||
}
|
||||
result += *it2;
|
||||
}
|
||||
result += "\"";
|
||||
}
|
||||
else
|
||||
{
|
||||
result += " " + it->first + "=\"" + it->second + "\"";
|
||||
}
|
||||
}
|
||||
result += NLMISC::toString(" data-debug=\"childIndex: %d\"", childIndex);
|
||||
result += ">";
|
||||
|
||||
if (tree)
|
||||
{
|
||||
result += "\n";
|
||||
for(std::list<CHtmlElement>::const_iterator it = Children.begin(); it != Children.end(); ++it)
|
||||
{
|
||||
result += it->toString(tree, depth+1);
|
||||
}
|
||||
if (depth > 0)
|
||||
{
|
||||
result += NLMISC::toString("[%d]", depth);
|
||||
result.append(depth, '-');
|
||||
}
|
||||
result += "</" + Value + ">\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Value.find("\n") == std::string::npos)
|
||||
{
|
||||
result += "{" + Value + "}";
|
||||
}
|
||||
else
|
||||
{
|
||||
result += "{";
|
||||
std::string::size_type start = 0;
|
||||
std::string::size_type pos = Value.find_first_of("\n\r\t");
|
||||
while(pos != std::string::npos)
|
||||
{
|
||||
result += Value.substr(start, pos - start);
|
||||
if (Value[pos] == '\n')
|
||||
{
|
||||
result += "⏎";
|
||||
}
|
||||
else if (Value[pos] == '\t')
|
||||
{
|
||||
result += "⇥";
|
||||
}
|
||||
|
||||
start = pos+1;
|
||||
pos = Value.find_first_of("\n\r\t", start);
|
||||
}
|
||||
result += "}";
|
||||
}
|
||||
if (tree) result += "\n";
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
} // namespace
|
Loading…
Reference in New Issue