Changed: Implement HTML renderer with CSS styling
--HG-- branch : develop
This commit is contained in:
parent
9587e9bcef
commit
fb54672815
18 changed files with 4587 additions and 301 deletions
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
#include "nel/misc/types_nl.h"
|
#include "nel/misc/types_nl.h"
|
||||||
#include "nel/gui/css_style.h"
|
#include "nel/gui/css_style.h"
|
||||||
|
#include "nel/gui/css_selector.h"
|
||||||
|
|
||||||
namespace NLGUI
|
namespace NLGUI
|
||||||
{
|
{
|
||||||
|
@ -31,8 +32,118 @@ namespace NLGUI
|
||||||
public:
|
public:
|
||||||
// parse style declaration, eg "color: red; font-size: 10px;"
|
// parse style declaration, eg "color: red; font-size: 10px;"
|
||||||
static TStyle parseDecls(const std::string &styleString);
|
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
|
}//namespace
|
||||||
|
|
||||||
#endif // CL_CSS_PARSER_H
|
#endif // CL_CSS_PARSER_H
|
||||||
|
|
107
code/nel/include/nel/gui/css_selector.h
Normal file
107
code/nel/include/nel/gui/css_selector.h
Normal file
|
@ -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
|
||||||
|
|
|
@ -19,9 +19,12 @@
|
||||||
|
|
||||||
#include "nel/misc/types_nl.h"
|
#include "nel/misc/types_nl.h"
|
||||||
#include "nel/misc/rgba.h"
|
#include "nel/misc/rgba.h"
|
||||||
|
#include "nel/gui/css_selector.h"
|
||||||
|
|
||||||
namespace NLGUI
|
namespace NLGUI
|
||||||
{
|
{
|
||||||
|
class CHtmlElement;
|
||||||
|
|
||||||
typedef std::map<std::string, std::string> TStyle;
|
typedef std::map<std::string, std::string> TStyle;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -62,6 +65,19 @@ namespace NLGUI
|
||||||
BackgroundColor=NLMISC::CRGBA::Black;
|
BackgroundColor=NLMISC::CRGBA::Black;
|
||||||
BackgroundColorOver=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 FontSize;
|
||||||
uint FontWeight;
|
uint FontWeight;
|
||||||
bool FontOblique;
|
bool FontOblique;
|
||||||
|
@ -78,11 +94,26 @@ namespace NLGUI
|
||||||
sint32 BorderWidth;
|
sint32 BorderWidth;
|
||||||
NLMISC::CRGBA BackgroundColor;
|
NLMISC::CRGBA BackgroundColor;
|
||||||
NLMISC::CRGBA BackgroundColorOver;
|
NLMISC::CRGBA BackgroundColorOver;
|
||||||
|
|
||||||
|
std::string WhiteSpace;
|
||||||
|
std::string TextAlign;
|
||||||
|
std::string VerticalAlign;
|
||||||
|
|
||||||
|
TStyle StyleRules;
|
||||||
};
|
};
|
||||||
|
|
||||||
class CCssStyle {
|
class CCssStyle {
|
||||||
public:
|
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>'
|
// 'browser' style, overwriten with '<html>'
|
||||||
CStyleParams Root;
|
CStyleParams Root;
|
||||||
|
@ -90,6 +121,9 @@ namespace NLGUI
|
||||||
// current element style
|
// current element style
|
||||||
CStyleParams Current;
|
CStyleParams Current;
|
||||||
|
|
||||||
|
// known style rules sorted by specificity
|
||||||
|
std::vector<SStyleRule> _StyleRules;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<CStyleParams> _StyleStack;
|
std::vector<CStyleParams> _StyleStack;
|
||||||
|
|
||||||
|
@ -98,10 +132,26 @@ namespace NLGUI
|
||||||
|
|
||||||
// read style attribute
|
// read style attribute
|
||||||
void getStyleParams(const std::string &styleString, CStyleParams &style, const CStyleParams ¤t) const;
|
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:
|
public:
|
||||||
void reset();
|
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
|
inline uint getFontSizeSmaller() const
|
||||||
{
|
{
|
||||||
if (Current.FontSize < 5)
|
if (Current.FontSize < 5)
|
||||||
|
@ -109,15 +159,29 @@ namespace NLGUI
|
||||||
return Current.FontSize-2;
|
return Current.FontSize-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sint styleStackIndex = 0;
|
||||||
|
|
||||||
inline void pushStyle()
|
inline void pushStyle()
|
||||||
{
|
{
|
||||||
|
styleStackIndex++;
|
||||||
_StyleStack.push_back(Current);
|
_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()
|
inline void popStyle()
|
||||||
{
|
{
|
||||||
|
styleStackIndex--;
|
||||||
if (_StyleStack.empty())
|
if (_StyleStack.empty())
|
||||||
|
{
|
||||||
Current = Root;
|
Current = Root;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Current = _StyleStack.back();
|
Current = _StyleStack.back();
|
||||||
|
@ -125,16 +189,32 @@ namespace NLGUI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply style string to this.Root
|
// apply style to this.Root
|
||||||
void applyRootStyle(const std::string &styleString);
|
void applyRootStyle(const std::string &styleString);
|
||||||
|
void applyRootStyle(const TStyle &styleRules);
|
||||||
|
|
||||||
// apply style string to this.Current
|
// apply style to this.Current
|
||||||
void applyStyle(const std::string &styleString);
|
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;
|
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
|
}//namespace
|
||||||
|
|
||||||
#endif // CL_CSS_STYLE_H
|
#endif // CL_CSS_STYLE_H
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "nel/gui/ctrl_button.h"
|
#include "nel/gui/ctrl_button.h"
|
||||||
#include "nel/gui/group_table.h"
|
#include "nel/gui/group_table.h"
|
||||||
#include "nel/gui/libwww_types.h"
|
#include "nel/gui/libwww_types.h"
|
||||||
|
#include "nel/gui/html_element.h"
|
||||||
#include "nel/gui/css_style.h"
|
#include "nel/gui/css_style.h"
|
||||||
|
|
||||||
// forward declaration
|
// forward declaration
|
||||||
|
@ -76,7 +77,7 @@ namespace NLGUI
|
||||||
static SWebOptions options;
|
static SWebOptions options;
|
||||||
|
|
||||||
// ImageDownload system
|
// ImageDownload system
|
||||||
enum TDataType {ImgType= 0, BnpType};
|
enum TDataType {ImgType= 0, BnpType, StylesheetType};
|
||||||
enum TImageType {NormalImage=0, OverImage};
|
enum TImageType {NormalImage=0, OverImage};
|
||||||
|
|
||||||
// Constructor
|
// Constructor
|
||||||
|
@ -97,6 +98,9 @@ namespace NLGUI
|
||||||
// Browse
|
// Browse
|
||||||
virtual void browse (const char *url);
|
virtual void browse (const char *url);
|
||||||
|
|
||||||
|
// load css from local file and insert into active stylesheet collection
|
||||||
|
void parseStylesheetFile(const std::string &fname);
|
||||||
|
|
||||||
// parse html string using libxml2 parser
|
// parse html string using libxml2 parser
|
||||||
bool parseHtml(const std::string &htmlString);
|
bool parseHtml(const std::string &htmlString);
|
||||||
|
|
||||||
|
@ -273,16 +277,10 @@ namespace NLGUI
|
||||||
virtual void addText (const char * buf, int len);
|
virtual void addText (const char * buf, int len);
|
||||||
|
|
||||||
// A new begin HTML element has been parsed (<IMG> for exemple)
|
// A new begin HTML element has been parsed (<IMG> for exemple)
|
||||||
virtual void beginElement (uint element_number, const std::vector<bool> &present, const std::vector<const char *> &value);
|
virtual void beginElement(CHtmlElement &elm);
|
||||||
|
|
||||||
// A new end HTML element has been parsed (</IMG> for exemple)
|
// A new end HTML element has been parsed (</IMG> for exemple)
|
||||||
virtual void endElement (uint element_number);
|
virtual void endElement(CHtmlElement &elm);
|
||||||
|
|
||||||
// A new begin unparsed element has been found
|
|
||||||
virtual void beginUnparsedElement(const char *buffer, int length);
|
|
||||||
|
|
||||||
// A new end unparsed element has been found
|
|
||||||
virtual void endUnparsedElement(const char *buffer, int length);
|
|
||||||
|
|
||||||
// Add GET params to the url
|
// Add GET params to the url
|
||||||
virtual void addHTTPGetParams (std::string &url, bool trustedDomain);
|
virtual void addHTTPGetParams (std::string &url, bool trustedDomain);
|
||||||
|
@ -296,6 +294,9 @@ namespace NLGUI
|
||||||
// Get Home URL
|
// Get Home URL
|
||||||
virtual std::string home();
|
virtual std::string home();
|
||||||
|
|
||||||
|
// parse dom node and all child nodes recursively
|
||||||
|
void renderDOM(CHtmlElement &elm);
|
||||||
|
|
||||||
// Clear style stack and restore default style
|
// Clear style stack and restore default style
|
||||||
void resetCssStyle();
|
void resetCssStyle();
|
||||||
|
|
||||||
|
@ -326,7 +327,7 @@ namespace NLGUI
|
||||||
void addString(const ucstring &str);
|
void addString(const ucstring &str);
|
||||||
|
|
||||||
// Add an image in the current paragraph
|
// Add an image in the current paragraph
|
||||||
void addImage(const std::string &id, const char *image, bool reloadImg=false, const CStyleParams &style = CStyleParams());
|
void addImage(const std::string &id, const std::string &img, bool reloadImg=false, const CStyleParams &style = CStyleParams());
|
||||||
|
|
||||||
// Add a text area in the current paragraph
|
// Add a text area in the current paragraph
|
||||||
CInterfaceGroup *addTextArea (const std::string &templateName, const char *name, uint rows, uint cols, bool multiLine, const ucstring &content, uint maxlength);
|
CInterfaceGroup *addTextArea (const std::string &templateName, const char *name, uint rows, uint cols, bool multiLine, const ucstring &content, uint maxlength);
|
||||||
|
@ -365,6 +366,14 @@ namespace NLGUI
|
||||||
// Current URL
|
// Current URL
|
||||||
std::string _DocumentUrl;
|
std::string _DocumentUrl;
|
||||||
std::string _DocumentDomain;
|
std::string _DocumentDomain;
|
||||||
|
std::string _DocumentHtml; // not updated, only set by first render
|
||||||
|
// If true, then render _DocumentHtml on next update (replaces content)
|
||||||
|
bool _RenderNextTime;
|
||||||
|
// true if renderer is waiting for css files to finish downloading (link rel=stylesheet)
|
||||||
|
bool _WaitingForStylesheet;
|
||||||
|
// list of css file urls that are queued up for download
|
||||||
|
std::vector<std::string> _StylesheetQueue;
|
||||||
|
|
||||||
// Valid base href was found
|
// Valid base href was found
|
||||||
bool _IgnoreBaseUrlTag;
|
bool _IgnoreBaseUrlTag;
|
||||||
// Fragment from loading url
|
// Fragment from loading url
|
||||||
|
@ -484,6 +493,11 @@ namespace NLGUI
|
||||||
|
|
||||||
// Keep track of current element style
|
// Keep track of current element style
|
||||||
CCssStyle _Style;
|
CCssStyle _Style;
|
||||||
|
CHtmlElement _HtmlDOM;
|
||||||
|
CHtmlElement *_CurrentHTMLElement;
|
||||||
|
// Backup of CurrentHTMLElement->nextSibling before ::beginElement() is called
|
||||||
|
// for luaParseHtml() to insert nodes into right place in right order
|
||||||
|
CHtmlElement *_CurrentHTMLNextSibling;
|
||||||
|
|
||||||
// Current link
|
// Current link
|
||||||
std::vector<std::string> _Link;
|
std::vector<std::string> _Link;
|
||||||
|
@ -722,6 +736,12 @@ namespace NLGUI
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class CHtmlParser;
|
friend class CHtmlParser;
|
||||||
|
// TODO: beginElement is overwritten in client quick help class, merge it here?
|
||||||
|
void beginElementDeprecated(uint element_number, const std::vector<bool> &present, const std::vector<const char *> &value);
|
||||||
|
void endElementDeprecated(uint element_number);
|
||||||
|
|
||||||
|
// move src->Children into CurrentHtmlElement.parent.children element
|
||||||
|
void spliceFragment(std::list<CHtmlElement>::iterator src);
|
||||||
|
|
||||||
// decode all HTML entities
|
// decode all HTML entities
|
||||||
static ucstring decodeHTMLEntities(const ucstring &str);
|
static ucstring decodeHTMLEntities(const ucstring &str);
|
||||||
|
@ -764,6 +784,8 @@ namespace NLGUI
|
||||||
int RunningCurls;
|
int RunningCurls;
|
||||||
|
|
||||||
bool startCurlDownload(CDataDownload &download);
|
bool startCurlDownload(CDataDownload &download);
|
||||||
|
void finishCurlDownload(CDataDownload &download);
|
||||||
|
void pumpCurlDownloads();
|
||||||
|
|
||||||
void initImageDownload();
|
void initImageDownload();
|
||||||
void checkImageDownload();
|
void checkImageDownload();
|
||||||
|
@ -782,11 +804,94 @@ namespace NLGUI
|
||||||
bool addBnpDownload(std::string url, const std::string &action, const std::string &script, const std::string &md5sum);
|
bool addBnpDownload(std::string url, const std::string &action, const std::string &script, const std::string &md5sum);
|
||||||
std::string localBnpName(const std::string &url);
|
std::string localBnpName(const std::string &url);
|
||||||
|
|
||||||
|
// add css file from <link href=".." rel="stylesheet"> to download queue
|
||||||
|
void addStylesheetDownload(std::vector<std::string> links);
|
||||||
|
|
||||||
void releaseDownloads();
|
void releaseDownloads();
|
||||||
void checkDownloads();
|
void checkDownloads();
|
||||||
|
|
||||||
// HtmlType download finished
|
// HtmlType download finished
|
||||||
void htmlDownloadFinished(const std::string &content, const std::string &type, long code);
|
void htmlDownloadFinished(const std::string &content, const std::string &type, long code);
|
||||||
|
|
||||||
|
// stylesheet finished downloading. if local file does not exist, then it failed (404)
|
||||||
|
void cssDownloadFinished(const std::string &url, const std::string &local);
|
||||||
|
|
||||||
|
// read common table/tr/td parameters and push them to _CellParams
|
||||||
|
void getCellsParameters(const CHtmlElement &elm, bool inherit);
|
||||||
|
|
||||||
|
// render _HtmlDOM
|
||||||
|
void renderDocument();
|
||||||
|
|
||||||
|
// :before, :after rendering
|
||||||
|
void renderPseudoElement(const std::string &pseudo, const CHtmlElement &elm);
|
||||||
|
|
||||||
|
// HTML elements
|
||||||
|
void htmlA(const CHtmlElement &elm);
|
||||||
|
void htmlAend(const CHtmlElement &elm);
|
||||||
|
void htmlBASE(const CHtmlElement &elm);
|
||||||
|
void htmlBODY(const CHtmlElement &elm);
|
||||||
|
void htmlBR(const CHtmlElement &elm);
|
||||||
|
void htmlDD(const CHtmlElement &elm);
|
||||||
|
void htmlDDend(const CHtmlElement &elm);
|
||||||
|
//void htmlDEL(const CHtmlElement &elm);
|
||||||
|
void htmlDIV(const CHtmlElement &elm);
|
||||||
|
void htmlDIVend(const CHtmlElement &elm);
|
||||||
|
void htmlDL(const CHtmlElement &elm);
|
||||||
|
void htmlDLend(const CHtmlElement &elm);
|
||||||
|
void htmlDT(const CHtmlElement &elm);
|
||||||
|
void htmlDTend(const CHtmlElement &elm);
|
||||||
|
//void htmlEM(const CHtmlElement &elm);
|
||||||
|
void htmlFONT(const CHtmlElement &elm);
|
||||||
|
void htmlFORM(const CHtmlElement &elm);
|
||||||
|
void htmlH(const CHtmlElement &elm);
|
||||||
|
void htmlHend(const CHtmlElement &elm);
|
||||||
|
void htmlHEAD(const CHtmlElement &elm);
|
||||||
|
void htmlHEADend(const CHtmlElement &elm);
|
||||||
|
void htmlHR(const CHtmlElement &elm);
|
||||||
|
void htmlHTML(const CHtmlElement &elm);
|
||||||
|
void htmlI(const CHtmlElement &elm);
|
||||||
|
void htmlIend(const CHtmlElement &elm);
|
||||||
|
void htmlIMG(const CHtmlElement &elm);
|
||||||
|
void htmlINPUT(const CHtmlElement &elm);
|
||||||
|
void htmlLI(const CHtmlElement &elm);
|
||||||
|
void htmlLIend(const CHtmlElement &elm);
|
||||||
|
void htmlLUA(const CHtmlElement &elm);
|
||||||
|
void htmlLUAend(const CHtmlElement &elm);
|
||||||
|
void htmlMETA(const CHtmlElement &elm);
|
||||||
|
void htmlOBJECT(const CHtmlElement &elm);
|
||||||
|
void htmlOBJECTend(const CHtmlElement &elm);
|
||||||
|
void htmlOL(const CHtmlElement &elm);
|
||||||
|
void htmlOLend(const CHtmlElement &elm);
|
||||||
|
void htmlOPTION(const CHtmlElement &elm);
|
||||||
|
void htmlOPTIONend(const CHtmlElement &elm);
|
||||||
|
void htmlP(const CHtmlElement &elm);
|
||||||
|
void htmlPend(const CHtmlElement &elm);
|
||||||
|
void htmlPRE(const CHtmlElement &elm);
|
||||||
|
void htmlPREend(const CHtmlElement &elm);
|
||||||
|
void htmlSCRIPT(const CHtmlElement &elm);
|
||||||
|
void htmlSCRIPTend(const CHtmlElement &elm);
|
||||||
|
void htmlSELECT(const CHtmlElement &elm);
|
||||||
|
void htmlSELECTend(const CHtmlElement &elm);
|
||||||
|
//void htmlSMALL(const CHtmlElement &elm);
|
||||||
|
//void htmlSPAN(const CHtmlElement &elm);
|
||||||
|
//void htmlSTRONG(const CHtmlElement &elm);
|
||||||
|
void htmlSTYLE(const CHtmlElement &elm);
|
||||||
|
void htmlSTYLEend(const CHtmlElement &elm);
|
||||||
|
void htmlTABLE(const CHtmlElement &elm);
|
||||||
|
void htmlTABLEend(const CHtmlElement &elm);
|
||||||
|
void htmlTD(const CHtmlElement &elm);
|
||||||
|
void htmlTDend(const CHtmlElement &elm);
|
||||||
|
void htmlTEXTAREA(const CHtmlElement &elm);
|
||||||
|
void htmlTEXTAREAend(const CHtmlElement &elm);
|
||||||
|
void htmlTH(const CHtmlElement &elm);
|
||||||
|
void htmlTHend(const CHtmlElement &elm);
|
||||||
|
void htmlTITLE(const CHtmlElement &elm);
|
||||||
|
void htmlTITLEend(const CHtmlElement &elm);
|
||||||
|
void htmlTR(const CHtmlElement &elm);
|
||||||
|
void htmlTRend(const CHtmlElement &elm);
|
||||||
|
//void htmlU(const CHtmlElement &elm);
|
||||||
|
void htmlUL(const CHtmlElement &elm);
|
||||||
|
void htmlULend(const CHtmlElement &elm);
|
||||||
};
|
};
|
||||||
|
|
||||||
// adapter group that store y offset for inputs inside an html form
|
// adapter group that store y offset for inputs inside an html form
|
||||||
|
|
86
code/nel/include/nel/gui/html_element.h
Normal file
86
code/nel/include/nel/gui/html_element.h
Normal file
|
@ -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
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
namespace NLGUI
|
namespace NLGUI
|
||||||
{
|
{
|
||||||
class CGroupHTML;
|
class CHtmlElement;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief HTML parsing
|
* \brief HTML parsing
|
||||||
|
@ -31,21 +31,21 @@ namespace NLGUI
|
||||||
class CHtmlParser
|
class CHtmlParser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CHtmlParser(CGroupHTML *group) : _GroupHtml(group)
|
bool parseHtml(std::string htmlString) const;
|
||||||
{}
|
|
||||||
|
|
||||||
bool parseHtml(std::string htmlString);
|
// 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:
|
private:
|
||||||
// libxml2 html parser functions
|
// iterate over libxml html tree, build DOM, and join all <style> tags together
|
||||||
void htmlElement(xmlNode *node, int element_number);
|
void parseNode(xmlNode *a_node, CHtmlElement &parent, std::string &styleString, std::vector<std::string> &links) const;
|
||||||
void parseNode(xmlNode *a_node);
|
|
||||||
|
|
||||||
private:
|
// read <style> tag and add its content to styleString
|
||||||
|
void parseStyle(xmlNode *a_node, std::string &styleString) const;
|
||||||
|
|
||||||
CGroupHTML *_GroupHtml;
|
// update parent/sibling in elm.Children
|
||||||
|
void reindexChilds(CHtmlElement &elm) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -297,6 +297,9 @@ namespace NLGUI
|
||||||
// Parse a HTML color
|
// Parse a HTML color
|
||||||
NLMISC::CRGBA getColor (const char *color);
|
NLMISC::CRGBA getColor (const char *color);
|
||||||
|
|
||||||
|
// return css color in rgba() format
|
||||||
|
std::string getRGBAString(const NLMISC::CRGBA &color);
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
|
|
||||||
const std::string &setCurrentDomain(const std::string &uri);
|
const std::string &setCurrentDomain(const std::string &uri);
|
||||||
|
|
|
@ -276,6 +276,17 @@ template <class T> T trimRightWhiteSpaces (const T &str)
|
||||||
return str.substr (0, end);
|
return str.substr (0, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if both first and last char are quotes (' or "), then remove them
|
||||||
|
template <class T> T trimQuotes (const T&str)
|
||||||
|
{
|
||||||
|
typename T::size_type size = str.size();
|
||||||
|
if (size == 0)
|
||||||
|
return str;
|
||||||
|
if (str[0] != str[size-1] && (str[0] != '"' || str[0] != '\''))
|
||||||
|
return str;
|
||||||
|
return str.substr(1, size - 1);
|
||||||
|
}
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
// **** DEPRECATED *****: PLEASE DON'T USE THESE METHODS BUT FUNCTIONS ABOVE toLower() and toUpper()
|
// **** DEPRECATED *****: PLEASE DON'T USE THESE METHODS BUT FUNCTIONS ABOVE toLower() and toUpper()
|
||||||
//////////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
#include "nel/misc/types_nl.h"
|
#include "nel/misc/types_nl.h"
|
||||||
#include "nel/gui/css_parser.h"
|
#include "nel/gui/css_parser.h"
|
||||||
#include "nel/gui/css_style.h"
|
#include "nel/gui/css_style.h"
|
||||||
|
#include "nel/gui/css_selector.h"
|
||||||
|
|
||||||
using namespace NLMISC;
|
using namespace NLMISC;
|
||||||
|
|
||||||
|
@ -54,5 +55,662 @@ namespace NLGUI
|
||||||
|
|
||||||
return styles;
|
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
|
} // namespace
|
||||||
|
|
||||||
|
|
314
code/nel/src/gui/css_selector.cpp
Normal file
314
code/nel/src/gui/css_selector.cpp
Normal file
|
@ -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
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "nel/misc/types_nl.h"
|
#include "nel/misc/types_nl.h"
|
||||||
|
#include "nel/gui/html_element.h"
|
||||||
#include "nel/gui/css_style.h"
|
#include "nel/gui/css_style.h"
|
||||||
#include "nel/gui/css_parser.h"
|
#include "nel/gui/css_parser.h"
|
||||||
#include "nel/gui/libwww.h"
|
#include "nel/gui/libwww.h"
|
||||||
|
@ -30,24 +31,236 @@ using namespace NLMISC;
|
||||||
|
|
||||||
namespace NLGUI
|
namespace NLGUI
|
||||||
{
|
{
|
||||||
|
uint CCssStyle::SStyleRule::specificity() const
|
||||||
|
{
|
||||||
|
uint count = 0;
|
||||||
|
for(uint i = 0; i < Selector.size(); ++i)
|
||||||
|
{
|
||||||
|
count += Selector[i].specificity();
|
||||||
|
}
|
||||||
|
// counted as element tag like DIV
|
||||||
|
if (!PseudoElement.empty())
|
||||||
|
{
|
||||||
|
count += 0x000001;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
void CCssStyle::reset()
|
void CCssStyle::reset()
|
||||||
{
|
{
|
||||||
|
_StyleRules.clear();
|
||||||
_StyleStack.clear();
|
_StyleStack.clear();
|
||||||
|
|
||||||
Root = CStyleParams();
|
Root = CStyleParams();
|
||||||
Current = CStyleParams();
|
Current = CStyleParams();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ***************************************************************************
|
||||||
|
// Sorting helper
|
||||||
|
struct CCssSpecificityPred
|
||||||
|
{
|
||||||
|
bool operator()(CCssStyle::SStyleRule lhs, CCssStyle::SStyleRule rhs) const
|
||||||
|
{
|
||||||
|
return lhs.specificity() < rhs.specificity();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ***************************************************************************
|
||||||
|
void CCssStyle::parseStylesheet(const std::string &styleString)
|
||||||
|
{
|
||||||
|
CCssParser parser;
|
||||||
|
parser.parseStylesheet(styleString, _StyleRules);
|
||||||
|
|
||||||
|
// keep the list sorted
|
||||||
|
std::stable_sort(_StyleRules.begin(), _StyleRules.end(), CCssSpecificityPred());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CCssStyle::getStyleFor(CHtmlElement &elm) const
|
||||||
|
{
|
||||||
|
std::vector<SStyleRule> mRules;
|
||||||
|
for (std::vector<SStyleRule>::const_iterator it = _StyleRules.begin(); it != _StyleRules.end(); ++it)
|
||||||
|
{
|
||||||
|
if (match(it->Selector, elm))
|
||||||
|
{
|
||||||
|
mRules.push_back(*it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
elm.Style.clear();
|
||||||
|
elm.StyleBefore.clear();
|
||||||
|
elm.StyleAfter.clear();
|
||||||
|
|
||||||
|
if (!mRules.empty())
|
||||||
|
{
|
||||||
|
// style is sorted by specificity (lowest first), eg. html, .class, html.class, #id, html#id.class
|
||||||
|
for(std::vector<SStyleRule>::const_iterator i = mRules.begin(); i != mRules.end(); ++i)
|
||||||
|
{
|
||||||
|
if (i->PseudoElement.empty())
|
||||||
|
{
|
||||||
|
merge(elm.Style, i->Properties);
|
||||||
|
}
|
||||||
|
else if (i->PseudoElement == ":before")
|
||||||
|
{
|
||||||
|
merge(elm.StyleBefore, i->Properties);
|
||||||
|
}
|
||||||
|
else if (i->PseudoElement == ":after")
|
||||||
|
{
|
||||||
|
merge(elm.StyleAfter, i->Properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// style from "style" attribute overrides <style>
|
||||||
|
if (elm.hasNonEmptyAttribute("style"))
|
||||||
|
{
|
||||||
|
TStyle styles = CCssParser::parseDecls(elm.getAttribute("style"));
|
||||||
|
merge(elm.Style, styles);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CCssStyle::merge(TStyle &dst, const TStyle &src) const
|
||||||
|
{
|
||||||
|
// TODO: does not use '!important' flag
|
||||||
|
for(TStyle::const_iterator it = src.begin(); it != src.end(); ++it)
|
||||||
|
{
|
||||||
|
dst[it->first] = it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CCssStyle::match(const std::vector<CCssSelector> &selector, const CHtmlElement &elm) const
|
||||||
|
{
|
||||||
|
if (selector.empty()) return false;
|
||||||
|
|
||||||
|
// first selector, '>' immediate parent
|
||||||
|
bool matches = false;
|
||||||
|
bool mustMatchNext = true;
|
||||||
|
bool matchGeneralChild = false;
|
||||||
|
bool matchGeneralSibling = false;
|
||||||
|
|
||||||
|
const CHtmlElement *child;
|
||||||
|
child = &elm;
|
||||||
|
std::vector<CCssSelector>::const_reverse_iterator ritSelector = selector.rbegin();
|
||||||
|
char matchCombinator = '\0';
|
||||||
|
while(ritSelector != selector.rend())
|
||||||
|
{
|
||||||
|
if (!child)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
matches = ritSelector->match(*child);
|
||||||
|
if (!matches && mustMatchNext)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matches)
|
||||||
|
{
|
||||||
|
if (matchCombinator == ' ')
|
||||||
|
{
|
||||||
|
if (!child->parent)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
child = child->parent;
|
||||||
|
// walk up the tree until there is match for current selector
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchCombinator == '~')
|
||||||
|
{
|
||||||
|
// any previous sibling must match current selector
|
||||||
|
if (!child->previousSibling)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
child = child->previousSibling;
|
||||||
|
|
||||||
|
// check siblings until there is match for current selector
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mustMatchNext = false;
|
||||||
|
switch(ritSelector->Combinator)
|
||||||
|
{
|
||||||
|
case '\0':
|
||||||
|
// default case when single selector
|
||||||
|
break;
|
||||||
|
case ' ':
|
||||||
|
{
|
||||||
|
// general child - match child->parent to current/previous selector
|
||||||
|
if (!child->parent)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
child = child->parent;
|
||||||
|
matchCombinator = ritSelector->Combinator;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '~':
|
||||||
|
{
|
||||||
|
// any previous sibling must match current selector
|
||||||
|
if (!child->previousSibling)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
child = child->previousSibling;
|
||||||
|
matchCombinator = ritSelector->Combinator;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '+':
|
||||||
|
{
|
||||||
|
// adjacent sibling - previous sibling must match previous selector
|
||||||
|
if (!child->previousSibling)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
child = child->previousSibling;
|
||||||
|
mustMatchNext = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '>':
|
||||||
|
{
|
||||||
|
// child of - immediate parent must match previous selector
|
||||||
|
if (!child->parent)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
child = child->parent;
|
||||||
|
mustMatchNext = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// should not reach
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
++ritSelector;
|
||||||
|
}
|
||||||
|
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
void CCssStyle::applyRootStyle(const std::string &styleString)
|
void CCssStyle::applyRootStyle(const std::string &styleString)
|
||||||
{
|
{
|
||||||
getStyleParams(styleString, Root, Root);
|
getStyleParams(styleString, Root, Root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ***************************************************************************
|
||||||
|
void CCssStyle::applyRootStyle(const TStyle &styleRules)
|
||||||
|
{
|
||||||
|
getStyleParams(styleRules, Root, Root);
|
||||||
|
}
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
void CCssStyle::applyStyle(const std::string &styleString)
|
void CCssStyle::applyStyle(const std::string &styleString)
|
||||||
{
|
{
|
||||||
|
if (styleString.empty()) return;
|
||||||
|
|
||||||
if (_StyleStack.empty())
|
if (_StyleStack.empty())
|
||||||
{
|
{
|
||||||
getStyleParams(styleString, Current, Root);
|
getStyleParams(styleString, Current, Root);
|
||||||
|
@ -58,6 +271,20 @@ namespace NLGUI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ***************************************************************************
|
||||||
|
void CCssStyle::applyStyle(const TStyle &styleRules)
|
||||||
|
{
|
||||||
|
if (_StyleStack.empty())
|
||||||
|
{
|
||||||
|
getStyleParams(styleRules, Current, Root);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
getStyleParams(styleRules, Current, _StyleStack.back());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************************************************
|
||||||
bool CCssStyle::scanCssLength(const std::string& str, uint32 &px) const
|
bool CCssStyle::scanCssLength(const std::string& str, uint32 &px) const
|
||||||
{
|
{
|
||||||
if (fromString(str, px))
|
if (fromString(str, px))
|
||||||
|
@ -90,14 +317,30 @@ namespace NLGUI
|
||||||
// style.StrikeThrough; // text-decoration: line-through; text-decoration-line: line-through;
|
// style.StrikeThrough; // text-decoration: line-through; text-decoration-line: line-through;
|
||||||
void CCssStyle::getStyleParams(const std::string &styleString, CStyleParams &style, const CStyleParams ¤t) const
|
void CCssStyle::getStyleParams(const std::string &styleString, CStyleParams &style, const CStyleParams ¤t) const
|
||||||
{
|
{
|
||||||
float tmpf;
|
|
||||||
TStyle styles = CCssParser::parseDecls(styleString);
|
TStyle styles = CCssParser::parseDecls(styleString);
|
||||||
TStyle::iterator it;
|
|
||||||
|
|
||||||
// first pass: get font-size for 'em' sizes
|
getStyleParams(styles, style, current);
|
||||||
// get TextColor value used as 'currentcolor'
|
}
|
||||||
for (it=styles.begin(); it != styles.end(); ++it)
|
|
||||||
|
void CCssStyle::getStyleParams(const TStyle &styleRules, CStyleParams &style, const CStyleParams ¤t) const
|
||||||
|
{
|
||||||
|
float tmpf;
|
||||||
|
TStyle::const_iterator it;
|
||||||
|
|
||||||
|
if(styleRules.empty())
|
||||||
{
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first pass:
|
||||||
|
// - get font-size for 'em' sizes
|
||||||
|
// - split shorthand to its parts
|
||||||
|
// - get TextColor value that could be used for 'currentcolor'
|
||||||
|
for (it=styleRules.begin(); it != styleRules.end(); ++it)
|
||||||
|
{
|
||||||
|
// update local copy of applied style
|
||||||
|
style.StyleRules[it->first] = it->second;
|
||||||
|
|
||||||
if (it->first == "color")
|
if (it->first == "color")
|
||||||
{
|
{
|
||||||
if (it->second == "inherit")
|
if (it->second == "inherit")
|
||||||
|
@ -180,19 +423,56 @@ namespace NLGUI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
if (it->first == "background")
|
||||||
|
{
|
||||||
|
parseBackgroundShorthand(it->second, style);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// second pass: rest of style
|
// second pass: rest of style
|
||||||
for (it=styles.begin(); it != styles.end(); ++it)
|
for (it=styleRules.begin(); it != styleRules.end(); ++it)
|
||||||
{
|
{
|
||||||
if (it->first == "border")
|
if (it->first == "border" || it->first == "border-width")
|
||||||
{
|
{
|
||||||
sint32 b;
|
// TODO: border: 1px solid red;
|
||||||
if (it->second == "none")
|
if (it->second == "inherit")
|
||||||
|
{
|
||||||
|
style.BorderWidth = current.BorderWidth;
|
||||||
|
}
|
||||||
|
else if (it->second == "none")
|
||||||
|
{
|
||||||
style.BorderWidth = 0;
|
style.BorderWidth = 0;
|
||||||
|
}
|
||||||
|
else if (it->second == "thin")
|
||||||
|
{
|
||||||
|
style.BorderWidth = 1;
|
||||||
|
}
|
||||||
|
else if (it->second == "medium")
|
||||||
|
{
|
||||||
|
style.BorderWidth = 3;
|
||||||
|
}
|
||||||
|
else if (it->second == "thick")
|
||||||
|
{
|
||||||
|
style.BorderWidth = 5;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
if (fromString(it->second, b))
|
{
|
||||||
style.BorderWidth = b;
|
std::string unit;
|
||||||
|
if (getCssLength(tmpf, unit, it->second.c_str()))
|
||||||
|
{
|
||||||
|
if (unit == "rem")
|
||||||
|
style.BorderWidth = Root.FontSize * tmpf;
|
||||||
|
else if (unit == "em")
|
||||||
|
style.BorderWidth = current.FontSize * tmpf;
|
||||||
|
else if (unit == "pt")
|
||||||
|
style.BorderWidth = tmpf / 0.75f;
|
||||||
|
else if (unit == "%")
|
||||||
|
style.BorderWidth = 0; // no support for % in border width
|
||||||
|
else
|
||||||
|
style.BorderWidth = tmpf;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
if (it->first == "font-style")
|
if (it->first == "font-style")
|
||||||
|
@ -366,6 +646,30 @@ namespace NLGUI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
if (it->first == "text-align")
|
||||||
|
{
|
||||||
|
if (it->second == "inherit")
|
||||||
|
style.TextAlign = current.TextAlign;
|
||||||
|
else if (it->second == "left" || it->second == "right" || it->second == "center" || it->second == "justify")
|
||||||
|
style.TextAlign = it->second;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (it->first == "vertical-align")
|
||||||
|
{
|
||||||
|
if (it->second == "inherit")
|
||||||
|
style.VerticalAlign = current.VerticalAlign;
|
||||||
|
else if (it->second == "top" || it->second == "middle" || it->second == "bottom")
|
||||||
|
style.VerticalAlign = it->second;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
if (it->first == "white-space")
|
||||||
|
{
|
||||||
|
if (it->second == "inherit")
|
||||||
|
style.WhiteSpace = current.WhiteSpace;
|
||||||
|
else if (it->second == "normal" || it->second == "nowrap" || it->second == "pre")
|
||||||
|
style.WhiteSpace = it->second;
|
||||||
|
}
|
||||||
|
else
|
||||||
if (it->first == "width")
|
if (it->first == "width")
|
||||||
{
|
{
|
||||||
std::string unit;
|
std::string unit;
|
||||||
|
@ -474,6 +778,172 @@ namespace NLGUI
|
||||||
style.StrikeThrough = current.StrikeThrough;
|
style.StrikeThrough = current.StrikeThrough;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CCssStyle::parseBackgroundShorthand(const std::string &value, CStyleParams &style) const
|
||||||
|
{
|
||||||
|
// background: url(image.jpg) top center / 200px 200px no-repeat fixed padding-box content-box red;
|
||||||
|
// background-image : url(image.jpg)
|
||||||
|
// background-position : top center
|
||||||
|
// background-size : 200px 200px
|
||||||
|
// background-repeat : no-repeat
|
||||||
|
// background-attachment : fixed
|
||||||
|
// background-origin : padding-box
|
||||||
|
// background-clip : content-box
|
||||||
|
// background-color : red
|
||||||
|
|
||||||
|
const uint nbProps = 8;
|
||||||
|
std::string props[nbProps] = {"background-image", "background-position", "background-size", "background-repeat",
|
||||||
|
"background-attachment", "background-origin", "background-clip", "background-color"};
|
||||||
|
std::string values[nbProps];
|
||||||
|
bool found[nbProps] = {false};
|
||||||
|
|
||||||
|
|
||||||
|
uint partIndex = 0;
|
||||||
|
std::vector<std::string> parts;
|
||||||
|
std::vector<std::string>::iterator it;
|
||||||
|
// FIXME: this will fail if url() contains ' ' chars
|
||||||
|
NLMISC::splitString(value, " ", parts);
|
||||||
|
|
||||||
|
bool failed = false;
|
||||||
|
for(uint index = 0; index < parts.size(); index++)
|
||||||
|
{
|
||||||
|
const std::string val = toLower(trim(parts[index]));
|
||||||
|
|
||||||
|
for(uint i = 0; i < nbProps; i++)
|
||||||
|
{
|
||||||
|
if (found[i])
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props[i] == "background-image")
|
||||||
|
{
|
||||||
|
if (val.substr(0, 4) == "url(")
|
||||||
|
{
|
||||||
|
// use original value as 'val' is lowercase
|
||||||
|
values[i] = parts[index];
|
||||||
|
found[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (props[i] == "background-position")
|
||||||
|
{
|
||||||
|
// TODO:
|
||||||
|
}
|
||||||
|
else if (props[i] == "background-size")
|
||||||
|
{
|
||||||
|
// TODO: [<length-percentage> | auto ]{1,2} cover | contain
|
||||||
|
}
|
||||||
|
else if (props[i] == "background-repeat")
|
||||||
|
{
|
||||||
|
if (val == "repeat-x" || val == "repeat-y" || val == "repeat" || val == "space" || val == "round" || val == "no-repeat")
|
||||||
|
{
|
||||||
|
if (val == "repeat-x")
|
||||||
|
{
|
||||||
|
values[i] = "repeat no-repeat";
|
||||||
|
}
|
||||||
|
else if (val == "repeat-y")
|
||||||
|
{
|
||||||
|
values[i] = "no-repeat repeat";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::string horiz = val;
|
||||||
|
std::string vert = val;
|
||||||
|
if (index+1 < parts.size())
|
||||||
|
{
|
||||||
|
std::string next = toLower(trim(parts[index+1]));
|
||||||
|
if (next == "repeat" || next == "space" || next == "round" || next == "no-repeat")
|
||||||
|
{
|
||||||
|
vert = next;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
values[i] = horiz + " " + vert;
|
||||||
|
}
|
||||||
|
|
||||||
|
found[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (props[i] == "background-attachment")
|
||||||
|
{
|
||||||
|
// TODO: scroll | fixed | local
|
||||||
|
}
|
||||||
|
else if (props[i] == "background-origin" || props[i] == "background-clip")
|
||||||
|
{
|
||||||
|
// same values for both
|
||||||
|
if (val == "padding-box" || val == "border-box" || val == "content-box")
|
||||||
|
{
|
||||||
|
values[i] = val;
|
||||||
|
found[i] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (props[i] == "background-color")
|
||||||
|
{
|
||||||
|
CRGBA color;
|
||||||
|
if (!scanHTMLColor(val.c_str(), color))
|
||||||
|
{
|
||||||
|
failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
values[i] = val;
|
||||||
|
// color should come as last item
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalidate whole rule
|
||||||
|
if (failed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply found styles
|
||||||
|
for(uint i = 0; i < nbProps; i++)
|
||||||
|
{
|
||||||
|
if (found[i])
|
||||||
|
{
|
||||||
|
style.StyleRules[props[i]] = values[i];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// fill in default if one is set
|
||||||
|
if (props[i] == "background-image")
|
||||||
|
{
|
||||||
|
style.StyleRules[props[i]] = "none";
|
||||||
|
}
|
||||||
|
else if (props[i] == "background-position")
|
||||||
|
{
|
||||||
|
//style.StyleRules[props[i]] = "0% 0%";
|
||||||
|
}
|
||||||
|
else if (props[i] == "background-size")
|
||||||
|
{
|
||||||
|
//style.StyleRules[props[i]] = "auto auto";
|
||||||
|
}
|
||||||
|
else if (props[i] == "background-repeat")
|
||||||
|
{
|
||||||
|
style.StyleRules[props[i]] = "repeat repeat";
|
||||||
|
}
|
||||||
|
else if(props[i] == "background-attachment")
|
||||||
|
{
|
||||||
|
//style.StyleRules[props[i]] = "scroll";
|
||||||
|
}
|
||||||
|
else if(props[i] == "background-origin")
|
||||||
|
{
|
||||||
|
//style.StyleRules[props[i]] = "padding-box";
|
||||||
|
}
|
||||||
|
else if (props[i] == "background-clip")
|
||||||
|
{
|
||||||
|
//style.StyleRules[props[i]] = "border-box";
|
||||||
|
}
|
||||||
|
else if (props[i] == "background-color")
|
||||||
|
{
|
||||||
|
style.StyleRules[props[i]] = "transparent";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
void CCssStyle::applyCssMinMax(sint32 &width, sint32 &height, sint32 minw, sint32 minh, sint32 maxw, sint32 maxh) const
|
void CCssStyle::applyCssMinMax(sint32 &width, sint32 &height, sint32 minw, sint32 minh, sint32 maxw, sint32 maxh) const
|
||||||
{
|
{
|
||||||
|
|
File diff suppressed because it is too large
Load diff
168
code/nel/src/gui/html_element.cpp
Normal file
168
code/nel/src/gui/html_element.cpp
Normal file
|
@ -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
|
|
@ -35,71 +35,40 @@ using namespace NLMISC;
|
||||||
|
|
||||||
namespace NLGUI
|
namespace NLGUI
|
||||||
{
|
{
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
void CHtmlParser::htmlElement(xmlNode *node, int element_number)
|
void CHtmlParser::parseStyle(xmlNode *a_node, std::string &styleString) const
|
||||||
{
|
{
|
||||||
SGML_dtd *HTML_DTD = HTML_dtd ();
|
xmlNode *node = a_node;
|
||||||
|
while(node)
|
||||||
if (element_number < HTML_ELEMENTS)
|
|
||||||
{
|
{
|
||||||
CXMLAutoPtr ptr;
|
if (node->type == XML_CDATA_SECTION_NODE)
|
||||||
// load attributes into libwww structs
|
|
||||||
std::vector<bool> present;
|
|
||||||
std::vector<const char *>value;
|
|
||||||
std::string strvalues[MAX_ATTRIBUTES];
|
|
||||||
present.resize(30, false);
|
|
||||||
value.resize(30);
|
|
||||||
|
|
||||||
uint nbAttributes = std::min(MAX_ATTRIBUTES, HTML_DTD->tags[element_number].number_of_attributes);
|
|
||||||
for(uint i=0; i<nbAttributes; i++)
|
|
||||||
{
|
{
|
||||||
std::string name;
|
styleString += (const char*)node->content;
|
||||||
name = toLower(std::string(HTML_DTD->tags[element_number].attributes[i].name));
|
}
|
||||||
ptr = xmlGetProp(node, (const xmlChar *)name.c_str());
|
else
|
||||||
if (ptr)
|
{
|
||||||
{
|
nlwarning("<style> tag has child elements other than cdata[%d]", node->type);
|
||||||
// copy xmlChar to string (xmlChar will be released)
|
|
||||||
strvalues[i] = (const char *)(ptr);
|
|
||||||
// now use string pointer in value[] array
|
|
||||||
value[i] = strvalues[i].c_str();
|
|
||||||
present[i] = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_GroupHtml->beginElement(element_number, present, value);
|
node = node->next;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_GroupHtml->beginUnparsedElement((const char *)(node->name), xmlStrlen(node->name));
|
|
||||||
}
|
|
||||||
|
|
||||||
// recursive - text content / child nodes
|
|
||||||
parseNode(node->children);
|
|
||||||
|
|
||||||
// closing tag
|
|
||||||
if (element_number < HTML_ELEMENTS)
|
|
||||||
{
|
|
||||||
_GroupHtml->endElement(element_number);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_GroupHtml->endUnparsedElement((const char *)(node->name), xmlStrlen(node->name));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
// recursive function to walk html document
|
// recursive function to walk html document
|
||||||
void CHtmlParser::parseNode(xmlNode *a_node)
|
void CHtmlParser::parseNode(xmlNode *a_node, CHtmlElement &parent, std::string &styleString, std::vector<std::string> &links) const
|
||||||
{
|
{
|
||||||
SGML_dtd *HTML_DTD = HTML_dtd ();
|
SGML_dtd *HTML_DTD = HTML_dtd ();
|
||||||
|
|
||||||
|
uint childIndex = 0;
|
||||||
uint element_number;
|
uint element_number;
|
||||||
xmlNode *node = a_node;
|
xmlNode *node = a_node;
|
||||||
while(node)
|
while(node)
|
||||||
{
|
{
|
||||||
if (node->type == XML_TEXT_NODE)
|
if (node->type == XML_TEXT_NODE)
|
||||||
{
|
{
|
||||||
_GroupHtml->addText((const char *)(node->content), xmlStrlen(node->content));
|
parent.Children.push_back(CHtmlElement(CHtmlElement::TEXT_NODE, (const char*)(node->content)));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
if (node->type == XML_ELEMENT_NODE)
|
if (node->type == XML_ELEMENT_NODE)
|
||||||
|
@ -111,7 +80,123 @@ namespace NLGUI
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlElement(node, element_number);
|
// get pointer to previous sibling
|
||||||
|
CHtmlElement *prevSibling = NULL;
|
||||||
|
if (!parent.Children.empty())
|
||||||
|
{
|
||||||
|
// skip text nodes
|
||||||
|
for(std::list<CHtmlElement>::reverse_iterator it = parent.Children.rbegin(); it != parent.Children.rend(); ++it)
|
||||||
|
{
|
||||||
|
if (it->Type == CHtmlElement::ELEMENT_NODE)
|
||||||
|
{
|
||||||
|
prevSibling = &(*it);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.Children.push_back(CHtmlElement(CHtmlElement::ELEMENT_NODE, toLower((const char*)node->name)));
|
||||||
|
CHtmlElement &elm = parent.Children.back();
|
||||||
|
elm.ID = element_number;
|
||||||
|
elm.parent = &parent;
|
||||||
|
elm.childIndex = childIndex;
|
||||||
|
|
||||||
|
// previous/next sibling that is ELEMENT_NODE
|
||||||
|
elm.previousSibling = prevSibling;
|
||||||
|
if (prevSibling)
|
||||||
|
{
|
||||||
|
prevSibling->nextSibling = &parent.Children.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
childIndex++;
|
||||||
|
|
||||||
|
// TODO: harvest <link type="css">, <style>, <img>
|
||||||
|
|
||||||
|
elm.Attributes.clear();
|
||||||
|
|
||||||
|
for (xmlAttr *cur_attr = node->properties; cur_attr; cur_attr = cur_attr->next) {
|
||||||
|
std::string key(toLower((const char *)(cur_attr->name)));
|
||||||
|
std::string value;
|
||||||
|
if (cur_attr->children)
|
||||||
|
{
|
||||||
|
value = (const char *)(cur_attr->children->content);
|
||||||
|
}
|
||||||
|
elm.Attributes[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elm.hasAttribute("class"))
|
||||||
|
{
|
||||||
|
std::vector<std::string> parts;
|
||||||
|
NLMISC::splitString(elm.getAttribute("class"), " ", parts);
|
||||||
|
for(uint i = 0; i<parts.size();++i)
|
||||||
|
{
|
||||||
|
elm.ClassNames.insert(toLower(trim(parts[i])));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elm.Value == "style")
|
||||||
|
{
|
||||||
|
// <style type="text/css" media="all, screen">
|
||||||
|
// ...
|
||||||
|
// </style>
|
||||||
|
bool useStyle = true;
|
||||||
|
if (elm.hasAttribute("media"))
|
||||||
|
{
|
||||||
|
std::string media = trim(toLower(elm.Attributes["media"]));
|
||||||
|
useStyle = media.empty() || media.find("all") != std::string::npos || media.find("screen") != std::string::npos;
|
||||||
|
|
||||||
|
// <style media="ryzom"> for ingame browser
|
||||||
|
useStyle = useStyle || media == "ryzom";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useStyle)
|
||||||
|
{
|
||||||
|
parseStyle(node->children, styleString);
|
||||||
|
}
|
||||||
|
// style tag is kept in dom
|
||||||
|
}
|
||||||
|
if (elm.Value == "link" && elm.getAttribute("rel") == "stylesheet")
|
||||||
|
{
|
||||||
|
bool useStyle = true;
|
||||||
|
if (elm.hasAttribute("media"))
|
||||||
|
{
|
||||||
|
std::string media = trim(toLower(elm.Attributes["media"]));
|
||||||
|
useStyle = media.empty() || media.find("all") != std::string::npos || media.find("screen") != std::string::npos;
|
||||||
|
|
||||||
|
// <style media="ryzom"> for ingame browser
|
||||||
|
useStyle = useStyle || media == "ryzom";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (useStyle)
|
||||||
|
{
|
||||||
|
links.push_back(elm.getAttribute("href"));
|
||||||
|
}
|
||||||
|
// link tag is kept in dom
|
||||||
|
}
|
||||||
|
else if (node->children)
|
||||||
|
{
|
||||||
|
parseNode(node->children, elm, styleString, links);
|
||||||
|
|
||||||
|
// must cleanup nested tags that libxml2 does not fix
|
||||||
|
// dt without end tag: <dl><dt><dt></dl>
|
||||||
|
// dd without end tag: <dl><dd><dd></dl>
|
||||||
|
if (!elm.Children.empty() && (elm.Value == "dt" || elm.Value == "dd"))
|
||||||
|
{
|
||||||
|
std::string tag = elm.Value;
|
||||||
|
std::list<CHtmlElement>::iterator it;
|
||||||
|
for(it = elm.Children.begin(); it != elm.Children.end(); ++it)
|
||||||
|
{
|
||||||
|
if (it->Type == CHtmlElement::ELEMENT_NODE && it->Value == tag)
|
||||||
|
{
|
||||||
|
// relocate this and remaining elements over to parent
|
||||||
|
parent.Children.splice(parent.Children.end(), elm.Children, it, elm.Children.end());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elm.reindexChilds();
|
||||||
|
parent.reindexChilds();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// move into next sibling
|
// move into next sibling
|
||||||
|
@ -298,13 +383,13 @@ namespace NLGUI
|
||||||
}
|
}
|
||||||
|
|
||||||
// ***************************************************************************
|
// ***************************************************************************
|
||||||
bool CHtmlParser::parseHtml(std::string htmlString)
|
void CHtmlParser::getDOM(std::string htmlString, CHtmlElement &dom, std::string &styleString, std::vector<std::string> &links) const
|
||||||
{
|
{
|
||||||
htmlParserCtxtPtr parser = htmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL, XML_CHAR_ENCODING_UTF8);
|
htmlParserCtxtPtr parser = htmlCreatePushParserCtxt(NULL, NULL, NULL, 0, NULL, XML_CHAR_ENCODING_UTF8);
|
||||||
if (!parser)
|
if (!parser)
|
||||||
{
|
{
|
||||||
nlwarning("Creating html parser context failed");
|
nlwarning("Creating html parser context failed");
|
||||||
return false;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlCtxtUseOptions(parser, HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING | HTML_PARSE_NONET);
|
htmlCtxtUseOptions(parser, HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING | HTML_PARSE_NONET);
|
||||||
|
@ -315,29 +400,26 @@ namespace NLGUI
|
||||||
htmlParseChunk(parser, htmlString.c_str(), htmlString.size(), 0);
|
htmlParseChunk(parser, htmlString.c_str(), htmlString.size(), 0);
|
||||||
htmlParseChunk(parser, "", 0, 1);
|
htmlParseChunk(parser, "", 0, 1);
|
||||||
|
|
||||||
bool success = true;
|
|
||||||
if (parser->myDoc)
|
if (parser->myDoc)
|
||||||
{
|
{
|
||||||
xmlNode *root = xmlDocGetRootElement(parser->myDoc);
|
xmlNode *root = xmlDocGetRootElement(parser->myDoc);
|
||||||
if (root)
|
if (root)
|
||||||
{
|
{
|
||||||
parseNode(root);
|
styleString.clear();
|
||||||
|
parseNode(root, dom, styleString, links);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nlwarning("html root node failed");
|
nlwarning("html root node failed");
|
||||||
success = false;
|
|
||||||
}
|
}
|
||||||
xmlFreeDoc(parser->myDoc);
|
xmlFreeDoc(parser->myDoc);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
nlwarning("htmlstring parsing failed");
|
nlwarning("htmlstring parsing failed");
|
||||||
success = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
htmlFreeParserCtxt(parser);
|
htmlFreeParserCtxt(parser);
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -599,6 +599,11 @@ namespace NLGUI
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: CSS Colors Level 4 support
|
||||||
|
// Whitespace syntax, aliases: rgb == rgba, hsl == hsla
|
||||||
|
// rgb(51 170 51 / 0.4) /* 40% opaque green */
|
||||||
|
// rgb(51 170 51 / 40%) /* 40% opaque green */
|
||||||
|
|
||||||
if (strnicmp(src, "rgb(", 4) == 0 || strnicmp(src, "rgba(", 5) == 0)
|
if (strnicmp(src, "rgb(", 4) == 0 || strnicmp(src, "rgba(", 5) == 0)
|
||||||
{
|
{
|
||||||
src += 4;
|
src += 4;
|
||||||
|
@ -743,6 +748,11 @@ namespace NLGUI
|
||||||
return dst;
|
return dst;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string getRGBAString(const CRGBA &color)
|
||||||
|
{
|
||||||
|
return toString("rgba(%d, %d, %d, %.1f)", color.R, color.G, color.B, color.A / 255.f);
|
||||||
|
}
|
||||||
|
|
||||||
// update HTTPCookies list
|
// update HTTPCookies list
|
||||||
static void receiveCookie(const char *nsformat, const std::string &domain, bool trusted)
|
static void receiveCookie(const char *nsformat, const std::string &domain, bool trusted)
|
||||||
{
|
{
|
||||||
|
|
|
@ -784,6 +784,8 @@ static HTTag tags[HTML_ELEMENTS] = {
|
||||||
{ "U" , gen_attr, HTML_GEN_ATTRIBUTES },
|
{ "U" , gen_attr, HTML_GEN_ATTRIBUTES },
|
||||||
{ "UL" , ul_attr, HTML_UL_ATTRIBUTES },
|
{ "UL" , ul_attr, HTML_UL_ATTRIBUTES },
|
||||||
{ "VAR" , gen_attr, HTML_GEN_ATTRIBUTES },
|
{ "VAR" , gen_attr, HTML_GEN_ATTRIBUTES },
|
||||||
|
//
|
||||||
|
{ "LUA" , gen_attr, HTML_GEN_ATTRIBUTES },
|
||||||
};
|
};
|
||||||
|
|
||||||
static SGML_dtd HTMLP_dtd = {
|
static SGML_dtd HTMLP_dtd = {
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
#include "nel/gui/group_list.h"
|
#include "nel/gui/group_list.h"
|
||||||
#include "nel/gui/group_paragraph.h"
|
#include "nel/gui/group_paragraph.h"
|
||||||
#include "nel/gui/libwww.h"
|
#include "nel/gui/libwww.h"
|
||||||
|
#include "nel/gui/html_element.h"
|
||||||
#include "interface_manager.h"
|
#include "interface_manager.h"
|
||||||
#include "nel/gui/action_handler.h"
|
#include "nel/gui/action_handler.h"
|
||||||
#include "nel/misc/xml_auto_ptr.h"
|
#include "nel/misc/xml_auto_ptr.h"
|
||||||
|
@ -215,33 +216,29 @@ void CGroupQuickHelp::setGroupTextSize (CInterfaceGroup *group, bool selected)
|
||||||
|
|
||||||
extern CActionsContext ActionsContext;
|
extern CActionsContext ActionsContext;
|
||||||
|
|
||||||
void CGroupQuickHelp::beginElement (uint element_number, const std::vector<bool> &present, const std::vector<const char *>&value)
|
void CGroupQuickHelp::beginElement(CHtmlElement &elm)
|
||||||
{
|
{
|
||||||
CGroupHTML::beginElement (element_number, present, value);
|
CGroupHTML::beginElement (elm);
|
||||||
|
|
||||||
// Paragraph ?
|
// Paragraph ?
|
||||||
switch(element_number)
|
switch(elm.ID)
|
||||||
{
|
{
|
||||||
case HTML_A:
|
case HTML_A:
|
||||||
// Quick help
|
// Quick help
|
||||||
if (_TrustedDomain && present[MY_HTML_A_Z_ACTION_SHORTCUT] && value[MY_HTML_A_Z_ACTION_SHORTCUT])
|
if (_TrustedDomain && elm.hasNonEmptyAttribute("z_action_shortcut"))
|
||||||
{
|
{
|
||||||
// Get the action category
|
// Get the action category
|
||||||
string category;
|
string category = elm.getAttribute("z_action_category");
|
||||||
if (present[MY_HTML_A_Z_ACTION_CATEGORY] && value[MY_HTML_A_Z_ACTION_CATEGORY])
|
|
||||||
category = value[MY_HTML_A_Z_ACTION_CATEGORY];
|
|
||||||
|
|
||||||
// Get the action params
|
// Get the action params
|
||||||
string params;
|
string params = elm.getAttribute("z_action_params");
|
||||||
if (present[MY_HTML_A_Z_ACTION_PARAMS] && value[MY_HTML_A_Z_ACTION_PARAMS])
|
|
||||||
params = value[MY_HTML_A_Z_ACTION_PARAMS];
|
|
||||||
|
|
||||||
// Get the action descriptor
|
// Get the action descriptor
|
||||||
CActionsManager *actionManager = ActionsContext.getActionsManager (category);
|
CActionsManager *actionManager = ActionsContext.getActionsManager (category);
|
||||||
if (actionManager)
|
if (actionManager)
|
||||||
{
|
{
|
||||||
const CActionsManager::TActionComboMap &actionCombo = actionManager->getActionComboMap ();
|
const CActionsManager::TActionComboMap &actionCombo = actionManager->getActionComboMap ();
|
||||||
CActionsManager::TActionComboMap::const_iterator ite = actionCombo.find (CAction::CName (value[MY_HTML_A_Z_ACTION_SHORTCUT], params.c_str()));
|
CActionsManager::TActionComboMap::const_iterator ite = actionCombo.find (CAction::CName (elm.getAttribute("z_action_shortcut").c_str(), params.c_str()));
|
||||||
if (ite != actionCombo.end())
|
if (ite != actionCombo.end())
|
||||||
{
|
{
|
||||||
addString (ite->second.toUCString());
|
addString (ite->second.toUCString());
|
||||||
|
@ -252,7 +249,7 @@ void CGroupQuickHelp::beginElement (uint element_number, const std::vector<bool>
|
||||||
|
|
||||||
case HTML_P:
|
case HTML_P:
|
||||||
// Get the action name
|
// Get the action name
|
||||||
if (present[MY_HTML_P_QUICK_HELP_EVENTS])
|
if (elm.hasAttribute("quick_help_events"))
|
||||||
{
|
{
|
||||||
// This page is a quick help
|
// This page is a quick help
|
||||||
_IsQuickHelp = true;
|
_IsQuickHelp = true;
|
||||||
|
@ -260,38 +257,29 @@ void CGroupQuickHelp::beginElement (uint element_number, const std::vector<bool>
|
||||||
_Steps.push_back (CStep());
|
_Steps.push_back (CStep());
|
||||||
CStep &step = _Steps.back();
|
CStep &step = _Steps.back();
|
||||||
|
|
||||||
if (value[MY_HTML_P_QUICK_HELP_EVENTS])
|
// Get the event names
|
||||||
|
string events = elm.getAttribute("quick_help_events");
|
||||||
|
if (!events.empty())
|
||||||
{
|
{
|
||||||
// Get the event names
|
uint first = 0;
|
||||||
string events = value[MY_HTML_P_QUICK_HELP_EVENTS];
|
while (first < events.size())
|
||||||
if (!events.empty())
|
|
||||||
{
|
{
|
||||||
uint first = 0;
|
// String end
|
||||||
while (first < events.size())
|
string::size_type last = events.find_first_of(" ", first);
|
||||||
{
|
if (last == string::npos)
|
||||||
// String end
|
last = events.size();
|
||||||
string::size_type last = events.find_first_of(" ", first);
|
|
||||||
if (last == string::npos)
|
|
||||||
last = events.size();
|
|
||||||
|
|
||||||
// Extract the string
|
// Extract the string
|
||||||
step.EventToComplete.insert (events.substr (first, last-first));
|
step.EventToComplete.insert (events.substr (first, last-first));
|
||||||
first = (uint)last+1;
|
first = (uint)last+1;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the condition
|
// Get the condition
|
||||||
if (present[MY_HTML_P_QUICK_HELP_CONDITION] && value[MY_HTML_P_QUICK_HELP_CONDITION])
|
step.Condition = elm.getAttribute("quick_help_condition");
|
||||||
{
|
|
||||||
step.Condition = value[MY_HTML_P_QUICK_HELP_CONDITION];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the action handlers to run
|
// Get the action handlers to run
|
||||||
if (present[MY_HTML_P_QUICK_HELP_LINK] && value[MY_HTML_P_QUICK_HELP_LINK])
|
step.URL = elm.getAttribute("quick_help_link");
|
||||||
{
|
|
||||||
step.URL = value[MY_HTML_P_QUICK_HELP_LINK];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
#include "nel/misc/types_nl.h"
|
#include "nel/misc/types_nl.h"
|
||||||
#include "nel/gui/group_html.h"
|
#include "nel/gui/group_html.h"
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Quick help group
|
* Quick help group
|
||||||
* \author Cyril 'Hulud' Corvazier
|
* \author Cyril 'Hulud' Corvazier
|
||||||
|
@ -48,7 +47,7 @@ private:
|
||||||
virtual void updateCoords();
|
virtual void updateCoords();
|
||||||
|
|
||||||
// From CGroupHTML
|
// From CGroupHTML
|
||||||
virtual void beginElement (uint element_number, const std::vector<bool> &present, const std::vector<const char *>&value);
|
virtual void beginElement (NLGUI::CHtmlElement &elm);
|
||||||
virtual void endBuild ();
|
virtual void endBuild ();
|
||||||
virtual void browse (const char *url);
|
virtual void browse (const char *url);
|
||||||
virtual std::string home();
|
virtual std::string home();
|
||||||
|
|
Loading…
Reference in a new issue