// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #include "stdpch.h" #include #include "nel/misc/types_nl.h" #include "nel/gui/css_parser.h" #include "nel/gui/css_style.h" #include "nel/gui/css_selector.h" using namespace NLMISC; #ifdef DEBUG_NEW #define new DEBUG_NEW #endif namespace NLGUI { // *************************************************************************** // Parse style declarations style, eg. "color:red; font-size: 10px;" // // key is converted to lowercase // value is left as is TStyleVec CCssParser::parseDecls(const std::string &styleString) { TStyleVec styles; std::vector elements; NLMISC::splitString(styleString, ";", elements); for(uint i = 0; i < elements.size(); ++i) { std::string::size_type pos; pos = elements[i].find_first_of(':'); if (pos != std::string::npos) { std::string key = trim(toLower(elements[i].substr(0, pos))); std::string value = trim(elements[i].substr(pos+1)); styles.push_back(TStylePair(key, value)); } } return styles; } // *************************************************************************** // Parse stylesheet, eg content from main.css file // // Return all found rules void CCssParser::parseStylesheet(const std::string &cssString, std::vector &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 selectors; NLMISC::explode(selectorString, ucstring(","), selectors); TStyleVec 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= 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 CCssParser::parse_selector(const ucstring &sel, std::string &pseudoElement) const { std::vector 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] == '"') { // skip over quoted value start = pos; pos++; while(pos < sel.size() && sel[pos] != sel[start]) { if (sel[pos] == '\\') { pos++; } pos++; } if (pos == sel.size()) break; } else if (sel[pos] == '\\') { pos++; } else if (!quote && sel[pos] == ']') { value = sel.substr(start, pos - start); break; } pos++; } // while 'value' if (pos == sel.size()) break; bool cs = true; // [value="attr" i] if (value.size() > 2 && value[value.size()-2] == ' ') { ucchar lastChar = value[value.size()-1]; if (lastChar == 'i' || lastChar == 'I' || lastChar == 's' || lastChar == 'S') { value = value.substr(0, value.size()-2); cs = !((lastChar == 'i' || lastChar == 'I')); } } current.addAttribute(key.toUtf8(), trimQuotes(value).toUtf8(), (char)op, cs); } // 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 (!current.empty()) { 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