diff options
author | Benjamin Paassen <bpaassen@techfak.uni-bielefeld.de> | 2014-12-05 16:09:53 +0100 |
---|---|---|
committer | Benjamin Paassen <bpaassen@techfak.uni-bielefeld.de> | 2014-12-05 16:09:53 +0100 |
commit | 30711f43a6cad9889ee1af8539658c4ca1f59854 (patch) | |
tree | d37c88faf794ad2632fc787ec1724c9ec08bdbd1 | |
parent | 30765a8bbf30aafad89a632afc39966c5b4029b8 (diff) |
Further work on implementing CSSRuleSets. This needs further testing, though.
-rw-r--r-- | src/core/BufferedCharReader.cpp | 2 | ||||
-rw-r--r-- | src/core/CSS.hpp | 18 | ||||
-rw-r--r-- | src/core/CodeTokenizer.cpp | 26 | ||||
-rw-r--r-- | src/core/CodeTokenizer.hpp | 4 | ||||
-rw-r--r-- | src/plugins/css/CSSParser.cpp | 66 | ||||
-rw-r--r-- | src/plugins/css/CSSParser.hpp | 32 | ||||
-rw-r--r-- | test/plugins/css/CSSParserTest.cpp | 31 |
7 files changed, 149 insertions, 30 deletions
diff --git a/src/core/BufferedCharReader.cpp b/src/core/BufferedCharReader.cpp index 23c219a..f3792ec 100644 --- a/src/core/BufferedCharReader.cpp +++ b/src/core/BufferedCharReader.cpp @@ -147,7 +147,7 @@ bool BufferedCharReader::readCharacterAtCursor(ReadCursor &cursor, char *c) // If data has been read, append it to the input buffer and try // again if (cnt > 0) { - buffer.emplace_back(buf.data()); + buffer.emplace_back(std::string(buf.data(), cnt)); continue; } diff --git a/src/core/CSS.hpp b/src/core/CSS.hpp index 3b4289a..4cf15be 100644 --- a/src/core/CSS.hpp +++ b/src/core/CSS.hpp @@ -70,17 +70,24 @@ struct Specificity { */ class RuleSet : public Managed { private: - const std::map<std::string, variant::Variant> values; + std::map<std::string, variant::Variant> rules; public: /** * Initializes an empty RuleSet. */ - RuleSet(Manager &mgr) : Managed(mgr), values() {} + RuleSet(Manager &mgr) : Managed(mgr), rules() {} - const std::map<std::string, variant::Variant> &getValues() const + std::map<std::string, variant::Variant> &getRules() { return rules; } + + const std::map<std::string, variant::Variant> &getRules() const { - return values; + return rules; + } + + void merge(Rooted<RuleSet> other) + { + rules.insert(other->rules.begin(), other->rules.end()); } }; @@ -244,7 +251,7 @@ private: const PseudoSelector pseudoSelector; ManagedVector<SelectorEdge> edges; Owned<RuleSet> ruleSet; - bool accepting; + bool accepting = false; /** * This is an internal method all getChildren variants refer to. @@ -254,7 +261,6 @@ private: const PseudoSelector *select); public: - /** * This initializes an empty SelectorNode with the given name and the * given PseudoSelector. diff --git a/src/core/CodeTokenizer.cpp b/src/core/CodeTokenizer.cpp index ce4afe5..e9c1bbf 100644 --- a/src/core/CodeTokenizer.cpp +++ b/src/core/CodeTokenizer.cpp @@ -59,10 +59,14 @@ bool CodeTokenizer::doPrepare(const Token &t, std::deque<Token> &peeked) state = CodeTokenizerState::IN_LINE_COMMENT; break; case CodeTokenMode::LINEBREAK: - peeked.push_back({it->second.id, t.content, t.startColumn, - t.startLine, t.endColumn, t.endLine}); - return true; + if (!ignoreLinebreaks) { + peeked.push_back({it->second.id, t.content, + t.startColumn, t.startLine, + t.endColumn, t.endLine}); + } + return !ignoreLinebreaks; default: + bool empty = true; if (t.tokenId == TOKEN_TEXT) { int begin = -1; for (size_t c = 0; c < t.content.length(); c++) { @@ -86,20 +90,22 @@ bool CodeTokenizer::doPrepare(const Token &t, std::deque<Token> &peeked) t.startColumn + begin, t.startLine, t.startColumn + (int)c, t.endLine}); begin = -1; + empty = false; } } } - if(begin >= 0){ - peeked.push_back(Token{ - TOKEN_TEXT, - t.content.substr(begin), - t.startColumn + begin, t.startLine, - t.endColumn, t.endLine}); + if (begin >= 0) { + peeked.push_back( + Token{TOKEN_TEXT, t.content.substr(begin), + t.startColumn + begin, t.startLine, + t.endColumn, t.endLine}); + empty = false; } } else { + empty = false; peeked.push_back(t); } - return true; + return !empty; } startToken = t; returnTokenId = it->second.id; diff --git a/src/core/CodeTokenizer.hpp b/src/core/CodeTokenizer.hpp index 247918e..43c7abb 100644 --- a/src/core/CodeTokenizer.hpp +++ b/src/core/CodeTokenizer.hpp @@ -101,6 +101,10 @@ public: * true. */ bool ignoreComments = false; + /** + * If you do not want linebreaks to be returned you can set this to true. + */ + bool ignoreLinebreaks = false; /** * diff --git a/src/plugins/css/CSSParser.cpp b/src/plugins/css/CSSParser.cpp index 85d8858..82ed7e1 100644 --- a/src/plugins/css/CSSParser.cpp +++ b/src/plugins/css/CSSParser.cpp @@ -64,12 +64,7 @@ static const TokenTreeNode CSS_ROOT{{{"{", CURLY_OPEN}, {"*/", COMMENT_CLOSE}, {"\"", DOUBLE_QUOTE}, {"\\", ESCAPE}, - // linux linebreak - {"\n", LINEBREAK}, - // windows linebreak - {"\r\n", LINEBREAK}, - // Mac OS linebreak - {"\r", LINEBREAK}}}; + {"\n", LINEBREAK}}}; static const std::map<int, CodeTokenDescriptor> CSS_DESCRIPTORS = { {COMMENT_OPEN, {CodeTokenMode::BLOCK_COMMENT_START, COMMENT}}, @@ -83,6 +78,7 @@ Rooted<Node> CSSParser::parse(std::istream &is, ParserContext &ctx) BufferedCharReader input{is}; CodeTokenizer tokenizer{input, CSS_ROOT, CSS_DESCRIPTORS}; tokenizer.ignoreComments = true; + tokenizer.ignoreLinebreaks = true; Rooted<SelectorNode> root = {new SelectorNode{ctx.manager, "root"}}; parseDocument(root, tokenizer, ctx); return root; @@ -97,16 +93,22 @@ void CSSParser::parseDocument(Rooted<SelectorNode> root, } tokenizer.resetPeek(); std::vector<Rooted<SelectorNode>> leafList; + // parse the SelectorTree for this ruleSet. parseSelectors(root, tokenizer, leafList, ctx); - // TODO: Parse Ruleset + // parse the RuleSet itself. + Rooted<RuleSet> ruleSet = parseRuleSet(tokenizer, ctx); for (auto &leaf : leafList) { - /* every leaf is an accepting node, if one considers the SelectorTree + /* + * every leaf is an accepting node, if one considers the SelectorTree * to be a finite state machine. This is relevant, if users do not use * the CSS Parser to parse actual Ruleset content but to construct a * SelectorTree just to identify a part of the DocumentTree. */ leaf->setAccepting(true); - //TODO: append RuleSets + /* + * similarly we append the found rules to all leafs. + */ + leaf->getRuleSet()->merge(ruleSet); } parseDocument(root, tokenizer, ctx); } @@ -289,7 +291,51 @@ Rooted<SelectorNode> CSSParser::parsePrimitiveSelector(CodeTokenizer &tokenizer, } } -// TODO: Add RuleSet parsing methods. +Rooted<RuleSet> CSSParser::parseRuleSet(CodeTokenizer &tokenizer, + ParserContext &ctx) +{ + Rooted<RuleSet> ruleSet{new RuleSet(ctx.manager)}; + // if we have no ruleset content, we return an empty ruleset. + Token t; + if (!expect(CURLY_OPEN, tokenizer, t, false, ctx)) { + return ruleSet; + } + // otherwise we parse the rules. + parseRules(tokenizer, ruleSet, ctx); + // and we expect closing curly braces. + expect(CURLY_CLOSE, tokenizer, t, true, ctx); + return ruleSet; +} + +void CSSParser::parseRules(CodeTokenizer &tokenizer, Rooted<RuleSet> ruleSet, + ParserContext &ctx) +{ + std::string key; + variant::Variant value; + while (parseRule(tokenizer, ctx, key, value)) { + ruleSet->getRules().insert({key, value}); + } +} + +bool CSSParser::parseRule(CodeTokenizer &tokenizer, ParserContext &ctx, + std::string &key, variant::Variant &value) +{ + Token t; + if (!expect(TOKEN_TEXT, tokenizer, t, false, ctx)) { + return false; + } + // if we find text that is the key first. + key = t.content; + // then we expect a : + expect(COLON, tokenizer, t, true, ctx); + // then the value + // TODO: Resolve key for appropriate parsing function here. + expect(STRING, tokenizer, t, true, ctx); + value = variant::Variant(t.content.c_str()); + // and a ; + expect(SEMICOLON, tokenizer, t, true, ctx); + return true; +} bool CSSParser::expect(int expectedType, CodeTokenizer &tokenizer, Token &t, bool force, ParserContext &ctx) diff --git a/src/plugins/css/CSSParser.hpp b/src/plugins/css/CSSParser.hpp index eb51dfa..27a483d 100644 --- a/src/plugins/css/CSSParser.hpp +++ b/src/plugins/css/CSSParser.hpp @@ -89,7 +89,30 @@ private: Rooted<SelectorNode> parsePrimitiveSelector(CodeTokenizer &tokenizer, ParserContext &ctx); - // TODO: Add RuleSet parsing methods. + /** + * Implements the RULESET Nonterminal, which parses an entire RuleSet. Note + * that we do not require RuleSets to be parsed. It is permitted to just + * insert Selector expressions. + */ + Rooted<RuleSet> parseRuleSet(CodeTokenizer &tokenizer, ParserContext &ctx); + + /** + * Implements the RULES Nonterminal, which parses CSSRules inside a RuleSet. + */ + void parseRules(CodeTokenizer &tokenizer, Rooted<RuleSet> ruleSet, + ParserContext &ctx); + + /** + * Implements the RULE Nonterminal, which parses one single CSSRule. Key + * and value are stored in the input references. + * + * @param key is a (possibly empty) string reference for the key found. + * @param value is a (possibly empty) Variant reference for the value found. + * + * @return true if a rule was found. + */ + bool parseRule(CodeTokenizer &tokenizer, ParserContext &ctx, + std::string &key, variant::Variant &value); /** * A convenience function to wrap around the tokenizer peek() function that @@ -112,9 +135,12 @@ public: /** * This parses the given input as CSS content as specified by the grammar * seen above. The return value is a Rooted reference to the root of the - * SelectorTree. - * TODO: The RuleSet at the respective node at the tree lists all CSS Style + * SelectorTree. SelectorTrees are documented in detail in the CSS.hpp + * The RuleSet at the respective node at the tree lists all CSS Style * rules that apply. + * Note that you are not required to insert CSS code containing actual + * rules. You are permitted to just insert a CSS Selector expression + * specifying some part of a DocumentTree you want to refer to. * * @param is is a reference to the input stream that should be parsed. * @param ctx is a reference to the context that should be used while diff --git a/test/plugins/css/CSSParserTest.cpp b/test/plugins/css/CSSParserTest.cpp index 20d0836..8880fc5 100644 --- a/test/plugins/css/CSSParserTest.cpp +++ b/test/plugins/css/CSSParserTest.cpp @@ -121,6 +121,37 @@ TEST(CSSParser, testParseSelectors) ASSERT_EQ(0, Ag->getEdges().size()); ASSERT_TRUE(Ag->isAccepting()); } + +TEST(CSSParser, testParseCSS) +{ + // create the CSS input + std::stringstream input; + input << "A, B A {\n"; + input << "/*\n"; + input << " * Some multiline\n"; + input << " * comment\n"; + input << " */\n"; + input << "\t ident1 : \"val1\";\n"; + input << "\t ident2 : \"val2\";\n"; + input << "}\n"; + input << "A:select(a,b) {\n"; + input << "\t ident3 : \"val3\";\n"; + input << "}\n"; + input << "A {\n"; + input << "\t ident1 : \"val4\";\n"; + input << "}\n"; + + + // initialize an empty parser context. + StandaloneParserContext ctx; + + // parse the input. + CSSParser instance; + Rooted<SelectorNode> root = instance.parse(input, ctx).cast<SelectorNode>(); + + +} + } } } |