From f1d432892ce158490bb564ba3d01982772439a81 Mon Sep 17 00:00:00 2001 From: Benjamin Paassen Date: Mon, 26 Jan 2015 23:55:41 +0100 Subject: Renamed CSS to Style (finally) --- src/core/CSS.cpp | 142 --------------- src/core/CSS.hpp | 405 ----------------------------------------- src/core/model/Style.cpp | 144 +++++++++++++++ src/core/model/Style.hpp | 407 ++++++++++++++++++++++++++++++++++++++++++ src/plugins/css/CSSParser.cpp | 87 ++++----- src/plugins/css/CSSParser.hpp | 24 +-- 6 files changed, 610 insertions(+), 599 deletions(-) delete mode 100644 src/core/CSS.cpp delete mode 100644 src/core/CSS.hpp create mode 100644 src/core/model/Style.cpp create mode 100644 src/core/model/Style.hpp (limited to 'src') diff --git a/src/core/CSS.cpp b/src/core/CSS.cpp deleted file mode 100644 index c42cf6c..0000000 --- a/src/core/CSS.cpp +++ /dev/null @@ -1,142 +0,0 @@ -/* - Ousía - Copyright (C) 2014 Benjamin Paaßen, Andreas Stöckel - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -#include "CSS.hpp" - -namespace ousia { - -void RuleSet::merge(Rooted other){ - for(auto& o : other->rules){ - rules[o.first] = o.second; - } -} - -/* - * different versions of "getChildren". - */ - -std::vector> SelectorNode::getChildren( - const SelectionOperator *op, const std::string *className, - const PseudoSelector *select) -{ - std::vector> out; - for (auto &e : edges) { - if (op && e->getSelectionOperator() != *op) { - continue; - } - if (className && e->getTarget()->getName() != *className) { - continue; - } - if (select && e->getTarget()->getPseudoSelector() != *select) { - continue; - } - out.push_back(e->getTarget()); - } - return out; -} - -std::vector> SelectorNode::getChildren( - const SelectionOperator &op, const std::string &className, - const PseudoSelector &select) -{ - return getChildren(&op, &className, &select); -} - -std::vector> SelectorNode::getChildren( - const std::string &className, const PseudoSelector &select) -{ - return getChildren(nullptr, &className, &select); -} - -std::vector> SelectorNode::getChildren( - const SelectionOperator &op, const PseudoSelector &select) -{ - return getChildren(&op, nullptr, &select); -} - -std::vector> SelectorNode::getChildren( - const SelectionOperator &op, const std::string &className) -{ - return getChildren(&op, &className, nullptr); -} - -std::vector> SelectorNode::getChildren( - const SelectionOperator &op) -{ - return getChildren(&op, nullptr, nullptr); -} - -std::vector> SelectorNode::getChildren( - const std::string &className) -{ - return getChildren(nullptr, &className, nullptr); -} - -std::vector> SelectorNode::getChildren( - const PseudoSelector &select) -{ - return getChildren(nullptr, nullptr, &select); -} - -std::vector> SelectorNode::getChildren() -{ - return getChildren(nullptr, nullptr, nullptr); -} - -/* - * append - */ - -std::vector> SelectorNode::append( - Handle edge) -{ - std::vector> out; - // look if we already have a child in an equivalent edge. - std::vector> children = - getChildren(edge->getSelectionOperator(), edge->getTarget()->getName(), - edge->getTarget()->getPseudoSelector()); - // note that this can only be one child or no child. - if (children.empty()) { - // if there is no child the appending process is trivial: We can just - // add the whole subtree represented by the other node as child here. - edges.push_back(edge); - } else { - // otherwise we start the appending process recursively on the child - // level. - // TODO: RuleSet merging - if (edge->getTarget()->getEdges().empty()) { - // if there are no more subsequent edges this is a leafe we could - // not merge, because it is already present in the Tree. - out.push_back(children[0]); - } else { - // otherwise we go into recursion. - for (auto &e : edge->getTarget()->getEdges()) { - Rooted e2 {e}; - std::vector> childLeafs = - children[0]->append(e2); - out.insert(out.end(), childLeafs.begin(), childLeafs.end()); - } - } - } - return out; -} - -std::vector> SelectorNode::append(Handle node){ - return append(new SelectorEdge{this->getManager(), node}); -} -} diff --git a/src/core/CSS.hpp b/src/core/CSS.hpp deleted file mode 100644 index 75ac73f..0000000 --- a/src/core/CSS.hpp +++ /dev/null @@ -1,405 +0,0 @@ -/* - Ousía - Copyright (C) 2014 Benjamin Paaßen, Andreas Stöckel - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - -/** - * @file CSS.hpp - - * @author Benjamin Paaßen (bpaassen@techfak.uni-bielefeld.de) - */ -#ifndef _OUSIA_CSS_HPP_ -#define _OUSIA_CSS_HPP_ - -#include -#include -#include - -#include -#include -#include - -namespace ousia { - -/* - * The Specificity or Precedence of a CSS RuleSet, which decides which - * rules are applied when different RuleSets contain conflicting information. - * - * The Specificity is calculated using the official W3C recommendation - * http://www.w3.org/TR/CSS2/cascade.html#specificity - * - * Note that we do not need to use the integer 'a', since we do not allow - * local style definitions for single nodes. - */ -struct Specificity { - int b; - int c; - int d; - - Specificity(int b, int c, int d) : b(b), c(c), d(d) {} - - friend bool operator<(const Specificity &x, const Specificity &y) - { - return std::tie(x.b, x.c, x.d) < std::tie(y.b, y.c, y.d); - } - - friend bool operator>(const Specificity &x, const Specificity &y) - { - return std::tie(x.b, x.c, x.d) > std::tie(y.b, y.c, y.d); - } - - friend bool operator==(const Specificity &x, const Specificity &y) - { - return std::tie(x.b, x.c, x.d) == std::tie(y.b, y.c, y.d); - } -}; - -/** - * The RuleSet class serves as a container class for key-value - * pairs. The values are TypeInstances. The proper type is - * implicitly defined by the keyword. - */ -class RuleSet : public Managed { -private: - std::map rules; - -public: - /** - * Initializes an empty RuleSet. - */ - RuleSet(Manager &mgr) : Managed(mgr), rules() {} - - std::map &getRules() { return rules; } - - const std::map &getRules() const - { - return rules; - } - - /** - * This implements an overriding "insert all" of all rules in the other - * RuleSet to the rules in this RuleSet. - */ - void merge(Rooted other); -}; - -/** - * PseudoSelectors are functions that change the behaviour of Selectors. - * They come in two different flavours: - * 1.) restricting PseudoSelectors are denoted as :my_selector(arg1,arg2,...) - * and are functions returning a boolean value given a node in the - * document tree and the additional arguments arg1, arg2, etc. - * If the function returns true the selector matches to the given document - * node. Otherwise it does not. Note that the #id notation is only - * syntactic sugar for the PseudoSelectors :has_id(id). Likewise - * the notation [attr] is a shorthand for :has_attribute(attr) and - * [attr="value"] is a horthand for :has_value(attr,value). - * 2.) generative PseudoSelectors are denoted as ::my_selector(arg1,arg2,...) - * and are functions returning a document node (probably a newly created - * one) referring to the element that shall be styled. An example is the - * CSS3 PseudoSelector ::first_letter which creates a new document node - * only containing the first letter of the text contained in the input - * document node, inserts it into the document tree and returns it to be - * styled. This mechanism also implies that generative PseudoSelectors - * only make sense at the end of a Selector Path (A B::my_selector C - * would not be a well-formed Selector). - * TODO: How do we control for this special case? - * - * Note that both restrictive and generative PseudoSelectors may be pre-defined - * and implemented in C++ code as well as user-defined and implemented as - * JavaScripts. The internal mechanism will resolve the given PseudoSelector - *name - * to the according implementation. - * - * Also note that the arguments of PseudoSelectors are always given as strings. - * PseudoSelector implementations have to ensure proper parsing of their inputs - * themselves. - */ -class PseudoSelector { -private: - const std::string name; - const Variant::arrayType args; - const bool generative; - -public: - PseudoSelector(std::string name, Variant::arrayType args, - bool generative) - : name(std::move(name)), args(std::move(args)), generative(generative) - { - } - - PseudoSelector(std::string name, bool generative) - : name(std::move(name)), args(), generative(generative) - { - } - - const std::string &getName() const { return name; } - - const Variant::arrayType &getArgs() const { return args; } - - const bool &isGenerative() const { return generative; } -}; - -inline bool operator==(const PseudoSelector &x, const PseudoSelector &y) -{ - return std::tie(x.getName(), x.getArgs(), x.isGenerative()) == - std::tie(y.getName(), y.getArgs(), y.isGenerative()); -} - -inline bool operator!=(const PseudoSelector &x, const PseudoSelector &y) -{ - return std::tie(x.getName(), x.getArgs(), x.isGenerative()) != - std::tie(y.getName(), y.getArgs(), y.isGenerative()); -} - -/** - * A SelectionOperator for now is just an enumeration class deciding - * whether a SelectorEdge builds a Descendant relationship or a - * (direct) child relationship. - */ -enum class SelectionOperator { DESCENDANT, DIRECT_DESCENDANT }; - -/** - * This represents a node in the SelectorTree. The SelectorTree makes it - * possible to efficiently resolve which elements of the documents are selected - * by a certain selector expression. - * - * Assume we have the following CSS specification. - * - * A B:p(a,b) { ruleset1 } - * - * A { ruleset2 } - * - * B::gp(c) { ruleset 3 } - * - * where p is a restricting pseudo-selector taking some arguments a and b and - * gp is a generating pseudo-selector taking some argument c. Both kinds of - * pseudo selectors result in a function (either C++ hard coded or JavaScript) - * that either returns a boolean, whether the current node in the document tree - * fulfils the restricting conditions (take :first_child, for example, which - * only returns true if the element is in fact the first child of its parent) - * or, in case of generative pseudo-selectors, returns a new element for the - * document tree (take ::first-letter for example, which takes the first letter - * of the text contained in a matching element of the document tree and - * generates a new node in the document tree containing just this letter such - * that it is possible to style it differently. - * - * The resulting style tree for our example would be - * - * A - ruleset 2 - * |_ B:p(a,b) - ruleset 1 - * B::gp(c) - ruleset 3 - * - * Given the document - * <A> - * <B/> - * <B/> - * </A> - * - * and assuming that the restricting pseudo-selector condition p only applied to - * the first B we get the following applications of RuleSets: - * - * A - ruleset 2 - * first B - ruleset 1 and ruleset 3 - * second B - ruleset 3 - * - * Furthermore, ruleset 1 has a higher precedence/specificity than ruleset 3. - * Therefore style rules contained in ruleset 3 will be overridden by - * contradicting style rules in ruleset 1. - */ -class SelectorNode : public Node { -public: - /* - * A SelectorEdge is a parent-to-child connection in the SelectorTree. - * We store edges in the parent. Accordingly SelectorEdges are - * defined by their target and the SelectionOperator specifying the - * kind of connection. - */ - class SelectorEdge : public Managed { - private: - Owned target; - const SelectionOperator selectionOperator; - - public: - SelectorEdge( - Manager &mgr, Handle target, - SelectionOperator selectionOperator = SelectionOperator::DESCENDANT) - : Managed(mgr), - target(acquire(target)), - selectionOperator(selectionOperator) - { - } - - Rooted getTarget() const { return target; } - - const SelectionOperator &getSelectionOperator() const - { - return selectionOperator; - } - }; - - // Content of the SelectorNode class. -private: - const PseudoSelector pseudoSelector; - ManagedVector edges; - Owned ruleSet; - bool accepting = false; - - /** - * This is an internal method all getChildren variants refer to. - */ - std::vector> getChildren(const SelectionOperator *op, - const std::string *className, - const PseudoSelector *select); - -public: - /** - * This initializes an empty SelectorNode with the given name and the - * given PseudoSelector. - */ - SelectorNode(Manager &mgr, std::string name, PseudoSelector pseudoSelector) - : Node(mgr, std::move(name)), - pseudoSelector(std::move(pseudoSelector)), - edges(this), - ruleSet(acquire(new RuleSet(mgr))) - { - } - - /** - * This initializes an empty SelectorNode with the given name and the - * trivial PseudoSelector "true". - */ - SelectorNode(Manager &mgr, std::string name) - : Node(mgr, std::move(name)), - pseudoSelector("true", false), - edges(this), - ruleSet(acquire(new RuleSet(mgr))) - { - } - - const PseudoSelector &getPseudoSelector() const { return pseudoSelector; } - - ManagedVector &getEdges() { return edges; } - - Rooted getRuleSet() const { return ruleSet; } - - /** - * This returns the child of this SelectorNode that is connected by - * the given operator, has the given className and the given - * PseudoSelector. For convention reasons with the other methods, this - * also returns a vector, which might either be empty or has exactly one - * element. - */ - std::vector> getChildren(const SelectionOperator &op, - const std::string &className, - const PseudoSelector &select); - - /** - * This returns all children of this SelectorNode that have the given - * className and the given PseudoSelector. - */ - std::vector> getChildren(const std::string &className, - const PseudoSelector &select); - - /** - * This returns all children of this SelectorNode that are connected by the - * given SelectionOperator and have the given PseudoSelector. - */ - std::vector> getChildren(const SelectionOperator &op, - const PseudoSelector &select); - - /** - * This returns all children of this SelectorNode that are connected by the - * given SelectionOperator and have the given className. - */ - std::vector> getChildren(const SelectionOperator &op, - const std::string &className); - - /** - * This returns all children of this SelectorNode that are connected by the - * given SelectionOperator. - */ - std::vector> getChildren(const SelectionOperator &op); - - /** - * This returns all children of this SelectorNode that have the given - * className. - */ - std::vector> getChildren(const std::string &className); - - /** - * This returns all children of this SelectorNode that have the given - * PseudoSelector. - */ - std::vector> getChildren(const PseudoSelector &select); - - /** - * This returns all children of this SelectorNode. - */ - std::vector> getChildren(); - - /** - * This appends the given edge and the subsequent SelectorTree to - * this SelectorNode. Note that only those nodes get appended to the - * SelectorTree that are not already contained in this SelectorTree. - * - * Consider the example of the following SelectorTree T: - * - * root - * | \ - * A B - * | - * C - * - * and the following SelectorEdge e with its subsequent Tree T_e - * - * | - * A - * |\ - * C D - * - * If we call root.append(e) the resulting SelectorTree looks like - * this: - * - * root - * | \ - * A B - * |\ - * C D - * - * The method returns all leafs of T that are equivalent to leafs of T_e - * and thus could not be appended to T, because they were already contained - * there. In our example this would be a vector containing just C. - * - * @param edge a Rooted reference to an edge that shall be appended to this - * SelectorNode. - * @return A list of leafs of this SelectorTree that could not be appended, - * because they were already contained. - */ - std::vector> append(Handle edge); - - /** - * This is just a convenience function which creates a new edge - * automatically using the DESCENDANT SelectionOperator. - */ - std::vector> append(Handle node); - - bool isAccepting() { return accepting; } - - void setAccepting(bool accepting) { this->accepting = accepting; } -}; -} -#endif diff --git a/src/core/model/Style.cpp b/src/core/model/Style.cpp new file mode 100644 index 0000000..b01b0c7 --- /dev/null +++ b/src/core/model/Style.cpp @@ -0,0 +1,144 @@ +/* + Ousía + Copyright (C) 2014 Benjamin Paaßen, Andreas Stöckel + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "Style.hpp" + +namespace ousia { +namespace model { + +void RuleSet::merge(Rooted other){ + for(auto& o : other->rules){ + rules[o.first] = o.second; + } +} + +/* + * different versions of "getChildren". + */ + +std::vector> SelectorNode::getChildren( + const SelectionOperator *op, const std::string *className, + const PseudoSelector *select) +{ + std::vector> out; + for (auto &e : edges) { + if (op && e->getSelectionOperator() != *op) { + continue; + } + if (className && e->getTarget()->getName() != *className) { + continue; + } + if (select && e->getTarget()->getPseudoSelector() != *select) { + continue; + } + out.push_back(e->getTarget()); + } + return out; +} + +std::vector> SelectorNode::getChildren( + const SelectionOperator &op, const std::string &className, + const PseudoSelector &select) +{ + return getChildren(&op, &className, &select); +} + +std::vector> SelectorNode::getChildren( + const std::string &className, const PseudoSelector &select) +{ + return getChildren(nullptr, &className, &select); +} + +std::vector> SelectorNode::getChildren( + const SelectionOperator &op, const PseudoSelector &select) +{ + return getChildren(&op, nullptr, &select); +} + +std::vector> SelectorNode::getChildren( + const SelectionOperator &op, const std::string &className) +{ + return getChildren(&op, &className, nullptr); +} + +std::vector> SelectorNode::getChildren( + const SelectionOperator &op) +{ + return getChildren(&op, nullptr, nullptr); +} + +std::vector> SelectorNode::getChildren( + const std::string &className) +{ + return getChildren(nullptr, &className, nullptr); +} + +std::vector> SelectorNode::getChildren( + const PseudoSelector &select) +{ + return getChildren(nullptr, nullptr, &select); +} + +std::vector> SelectorNode::getChildren() +{ + return getChildren(nullptr, nullptr, nullptr); +} + +/* + * append + */ + +std::vector> SelectorNode::append( + Handle edge) +{ + std::vector> out; + // look if we already have a child in an equivalent edge. + std::vector> children = + getChildren(edge->getSelectionOperator(), edge->getTarget()->getName(), + edge->getTarget()->getPseudoSelector()); + // note that this can only be one child or no child. + if (children.empty()) { + // if there is no child the appending process is trivial: We can just + // add the whole subtree represented by the other node as child here. + edges.push_back(edge); + } else { + // otherwise we start the appending process recursively on the child + // level. + // TODO: RuleSet merging + if (edge->getTarget()->getEdges().empty()) { + // if there are no more subsequent edges this is a leafe we could + // not merge, because it is already present in the Tree. + out.push_back(children[0]); + } else { + // otherwise we go into recursion. + for (auto &e : edge->getTarget()->getEdges()) { + Rooted e2 {e}; + std::vector> childLeafs = + children[0]->append(e2); + out.insert(out.end(), childLeafs.begin(), childLeafs.end()); + } + } + } + return out; +} + +std::vector> SelectorNode::append(Handle node){ + return append(new SelectorEdge{this->getManager(), node}); +} +} +} diff --git a/src/core/model/Style.hpp b/src/core/model/Style.hpp new file mode 100644 index 0000000..673f51e --- /dev/null +++ b/src/core/model/Style.hpp @@ -0,0 +1,407 @@ +/* + Ousía + Copyright (C) 2014 Benjamin Paaßen, Andreas Stöckel + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +/** + * @file Style.hpp + + * @author Benjamin Paaßen (bpaassen@techfak.uni-bielefeld.de) + */ +#ifndef _OUSIA_STYLE_HPP_ +#define _OUSIA_STYLE_HPP_ + +#include +#include +#include + +#include +#include +#include + +namespace ousia { +namespace model { + +/* + * The Specificity or Precedence of a CSS RuleSet, which decides which + * rules are applied when different RuleSets contain conflicting information. + * + * The Specificity is calculated using the official W3C recommendation + * http://www.w3.org/TR/CSS2/cascade.html#specificity + * + * Note that we do not need to use the integer 'a', since we do not allow + * local style definitions for single nodes. + */ +struct Specificity { + int b; + int c; + int d; + + Specificity(int b, int c, int d) : b(b), c(c), d(d) {} + + friend bool operator<(const Specificity &x, const Specificity &y) + { + return std::tie(x.b, x.c, x.d) < std::tie(y.b, y.c, y.d); + } + + friend bool operator>(const Specificity &x, const Specificity &y) + { + return std::tie(x.b, x.c, x.d) > std::tie(y.b, y.c, y.d); + } + + friend bool operator==(const Specificity &x, const Specificity &y) + { + return std::tie(x.b, x.c, x.d) == std::tie(y.b, y.c, y.d); + } +}; + +/** + * The RuleSet class serves as a container class for key-value + * pairs. The values are TypeInstances. The proper type is + * implicitly defined by the keyword. + */ +class RuleSet : public Managed { +private: + std::map rules; + +public: + /** + * Initializes an empty RuleSet. + */ + RuleSet(Manager &mgr) : Managed(mgr), rules() {} + + std::map &getRules() { return rules; } + + const std::map &getRules() const + { + return rules; + } + + /** + * This implements an overriding "insert all" of all rules in the other + * RuleSet to the rules in this RuleSet. + */ + void merge(Rooted other); +}; + +/** + * PseudoSelectors are functions that change the behaviour of Selectors. + * They come in two different flavours: + * 1.) restricting PseudoSelectors are denoted as :my_selector(arg1,arg2,...) + * and are functions returning a boolean value given a node in the + * document tree and the additional arguments arg1, arg2, etc. + * If the function returns true the selector matches to the given document + * node. Otherwise it does not. Note that the #id notation is only + * syntactic sugar for the PseudoSelectors :has_id(id). Likewise + * the notation [attr] is a shorthand for :has_attribute(attr) and + * [attr="value"] is a horthand for :has_value(attr,value). + * 2.) generative PseudoSelectors are denoted as ::my_selector(arg1,arg2,...) + * and are functions returning a document node (probably a newly created + * one) referring to the element that shall be styled. An example is the + * CSS3 PseudoSelector ::first_letter which creates a new document node + * only containing the first letter of the text contained in the input + * document node, inserts it into the document tree and returns it to be + * styled. This mechanism also implies that generative PseudoSelectors + * only make sense at the end of a Selector Path (A B::my_selector C + * would not be a well-formed Selector). + * TODO: How do we control for this special case? + * + * Note that both restrictive and generative PseudoSelectors may be pre-defined + * and implemented in C++ code as well as user-defined and implemented as + * JavaScripts. The internal mechanism will resolve the given PseudoSelector + *name + * to the according implementation. + * + * Also note that the arguments of PseudoSelectors are always given as strings. + * PseudoSelector implementations have to ensure proper parsing of their inputs + * themselves. + */ +class PseudoSelector { +private: + const std::string name; + const Variant::arrayType args; + const bool generative; + +public: + PseudoSelector(std::string name, Variant::arrayType args, + bool generative) + : name(std::move(name)), args(std::move(args)), generative(generative) + { + } + + PseudoSelector(std::string name, bool generative) + : name(std::move(name)), args(), generative(generative) + { + } + + const std::string &getName() const { return name; } + + const Variant::arrayType &getArgs() const { return args; } + + const bool &isGenerative() const { return generative; } +}; + +inline bool operator==(const PseudoSelector &x, const PseudoSelector &y) +{ + return std::tie(x.getName(), x.getArgs(), x.isGenerative()) == + std::tie(y.getName(), y.getArgs(), y.isGenerative()); +} + +inline bool operator!=(const PseudoSelector &x, const PseudoSelector &y) +{ + return std::tie(x.getName(), x.getArgs(), x.isGenerative()) != + std::tie(y.getName(), y.getArgs(), y.isGenerative()); +} + +/** + * A SelectionOperator for now is just an enumeration class deciding + * whether a SelectorEdge builds a Descendant relationship or a + * (direct) child relationship. + */ +enum class SelectionOperator { DESCENDANT, DIRECT_DESCENDANT }; + +/** + * This represents a node in the SelectorTree. The SelectorTree makes it + * possible to efficiently resolve which elements of the documents are selected + * by a certain selector expression. + * + * Assume we have the following CSS specification. + * + * A B:p(a,b) { ruleset1 } + * + * A { ruleset2 } + * + * B::gp(c) { ruleset 3 } + * + * where p is a restricting pseudo-selector taking some arguments a and b and + * gp is a generating pseudo-selector taking some argument c. Both kinds of + * pseudo selectors result in a function (either C++ hard coded or JavaScript) + * that either returns a boolean, whether the current node in the document tree + * fulfils the restricting conditions (take :first_child, for example, which + * only returns true if the element is in fact the first child of its parent) + * or, in case of generative pseudo-selectors, returns a new element for the + * document tree (take ::first-letter for example, which takes the first letter + * of the text contained in a matching element of the document tree and + * generates a new node in the document tree containing just this letter such + * that it is possible to style it differently. + * + * The resulting style tree for our example would be + * + * A - ruleset 2 + * |_ B:p(a,b) - ruleset 1 + * B::gp(c) - ruleset 3 + * + * Given the document + * <A> + * <B/> + * <B/> + * </A> + * + * and assuming that the restricting pseudo-selector condition p only applied to + * the first B we get the following applications of RuleSets: + * + * A - ruleset 2 + * first B - ruleset 1 and ruleset 3 + * second B - ruleset 3 + * + * Furthermore, ruleset 1 has a higher precedence/specificity than ruleset 3. + * Therefore style rules contained in ruleset 3 will be overridden by + * contradicting style rules in ruleset 1. + */ +class SelectorNode : public Node { +public: + /* + * A SelectorEdge is a parent-to-child connection in the SelectorTree. + * We store edges in the parent. Accordingly SelectorEdges are + * defined by their target and the SelectionOperator specifying the + * kind of connection. + */ + class SelectorEdge : public Managed { + private: + Owned target; + const SelectionOperator selectionOperator; + + public: + SelectorEdge( + Manager &mgr, Handle target, + SelectionOperator selectionOperator = SelectionOperator::DESCENDANT) + : Managed(mgr), + target(acquire(target)), + selectionOperator(selectionOperator) + { + } + + Rooted getTarget() const { return target; } + + const SelectionOperator &getSelectionOperator() const + { + return selectionOperator; + } + }; + + // Content of the SelectorNode class. +private: + const PseudoSelector pseudoSelector; + ManagedVector edges; + Owned ruleSet; + bool accepting = false; + + /** + * This is an internal method all getChildren variants refer to. + */ + std::vector> getChildren(const SelectionOperator *op, + const std::string *className, + const PseudoSelector *select); + +public: + /** + * This initializes an empty SelectorNode with the given name and the + * given PseudoSelector. + */ + SelectorNode(Manager &mgr, std::string name, PseudoSelector pseudoSelector) + : Node(mgr, std::move(name)), + pseudoSelector(std::move(pseudoSelector)), + edges(this), + ruleSet(acquire(new RuleSet(mgr))) + { + } + + /** + * This initializes an empty SelectorNode with the given name and the + * trivial PseudoSelector "true". + */ + SelectorNode(Manager &mgr, std::string name) + : Node(mgr, std::move(name)), + pseudoSelector("true", false), + edges(this), + ruleSet(acquire(new RuleSet(mgr))) + { + } + + const PseudoSelector &getPseudoSelector() const { return pseudoSelector; } + + ManagedVector &getEdges() { return edges; } + + Rooted getRuleSet() const { return ruleSet; } + + /** + * This returns the child of this SelectorNode that is connected by + * the given operator, has the given className and the given + * PseudoSelector. For convention reasons with the other methods, this + * also returns a vector, which might either be empty or has exactly one + * element. + */ + std::vector> getChildren(const SelectionOperator &op, + const std::string &className, + const PseudoSelector &select); + + /** + * This returns all children of this SelectorNode that have the given + * className and the given PseudoSelector. + */ + std::vector> getChildren(const std::string &className, + const PseudoSelector &select); + + /** + * This returns all children of this SelectorNode that are connected by the + * given SelectionOperator and have the given PseudoSelector. + */ + std::vector> getChildren(const SelectionOperator &op, + const PseudoSelector &select); + + /** + * This returns all children of this SelectorNode that are connected by the + * given SelectionOperator and have the given className. + */ + std::vector> getChildren(const SelectionOperator &op, + const std::string &className); + + /** + * This returns all children of this SelectorNode that are connected by the + * given SelectionOperator. + */ + std::vector> getChildren(const SelectionOperator &op); + + /** + * This returns all children of this SelectorNode that have the given + * className. + */ + std::vector> getChildren(const std::string &className); + + /** + * This returns all children of this SelectorNode that have the given + * PseudoSelector. + */ + std::vector> getChildren(const PseudoSelector &select); + + /** + * This returns all children of this SelectorNode. + */ + std::vector> getChildren(); + + /** + * This appends the given edge and the subsequent SelectorTree to + * this SelectorNode. Note that only those nodes get appended to the + * SelectorTree that are not already contained in this SelectorTree. + * + * Consider the example of the following SelectorTree T: + * + * root + * | \ + * A B + * | + * C + * + * and the following SelectorEdge e with its subsequent Tree T_e + * + * | + * A + * |\ + * C D + * + * If we call root.append(e) the resulting SelectorTree looks like + * this: + * + * root + * | \ + * A B + * |\ + * C D + * + * The method returns all leafs of T that are equivalent to leafs of T_e + * and thus could not be appended to T, because they were already contained + * there. In our example this would be a vector containing just C. + * + * @param edge a Rooted reference to an edge that shall be appended to this + * SelectorNode. + * @return A list of leafs of this SelectorTree that could not be appended, + * because they were already contained. + */ + std::vector> append(Handle edge); + + /** + * This is just a convenience function which creates a new edge + * automatically using the DESCENDANT SelectionOperator. + */ + std::vector> append(Handle node); + + bool isAccepting() { return accepting; } + + void setAccepting(bool accepting) { this->accepting = accepting; } +}; +} +} +#endif diff --git a/src/plugins/css/CSSParser.cpp b/src/plugins/css/CSSParser.cpp index cf92d32..40179bf 100644 --- a/src/plugins/css/CSSParser.cpp +++ b/src/plugins/css/CSSParser.cpp @@ -79,12 +79,13 @@ Rooted CSSParser::doParse(CharReader &reader, ParserContext &ctx) CodeTokenizer tokenizer{reader, CSS_ROOT, CSS_DESCRIPTORS}; tokenizer.ignoreComments = true; tokenizer.ignoreLinebreaks = true; - Rooted root = {new SelectorNode{ctx.getManager(), "root"}}; + Rooted root = { + new model::SelectorNode{ctx.getManager(), "root"}}; parseDocument(root, tokenizer, ctx); return root; } -void CSSParser::parseDocument(Rooted root, +void CSSParser::parseDocument(Rooted root, CodeTokenizer &tokenizer, ParserContext &ctx) { Token t; @@ -92,11 +93,11 @@ void CSSParser::parseDocument(Rooted root, return; } tokenizer.resetPeek(); - std::vector> leafList; + std::vector> leafList; // parse the SelectorTree for this ruleSet. parseSelectors(root, tokenizer, leafList, ctx); // parse the RuleSet itself. - Rooted ruleSet = parseRuleSet(tokenizer, ctx); + Rooted ruleSet = parseRuleSet(tokenizer, ctx); for (auto &leaf : leafList) { /* * every leaf is an accepting node, if one considers the SelectorTree @@ -113,14 +114,14 @@ void CSSParser::parseDocument(Rooted root, parseDocument(root, tokenizer, ctx); } -void CSSParser::parseSelectors(Rooted root, - CodeTokenizer &tokenizer, - std::vector> &leafList, - ParserContext &ctx) +void CSSParser::parseSelectors( + Rooted root, CodeTokenizer &tokenizer, + std::vector> &leafList, ParserContext &ctx) { auto tuple = parseSelector(tokenizer, ctx); // append the SelectorPath to the root node. - std::vector> unmergedLeafs = root->append(tuple.first); + std::vector> unmergedLeafs = + root->append(tuple.first); // append the leaf to the leafList. switch (unmergedLeafs.size()) { case 0: @@ -146,10 +147,10 @@ void CSSParser::parseSelectors(Rooted root, } } -std::pair, Rooted> CSSParser::parseSelector( - CodeTokenizer &tokenizer, ParserContext &ctx) +std::pair, Rooted> +CSSParser::parseSelector(CodeTokenizer &tokenizer, ParserContext &ctx) { - Rooted s = parsePrimitiveSelector(tokenizer, ctx); + Rooted s = parsePrimitiveSelector(tokenizer, ctx); Token t; if (!tokenizer.peek(t)) { // if we are at the end the found selector is the immediate child as @@ -164,8 +165,8 @@ std::pair, Rooted> CSSParser::parseSelector( // so we parse the rest of the subsequent SelectorPath auto tuple = parseSelector(tokenizer, ctx); // then we establish the DESCENDANT relationship - s->getEdges().push_back( - new SelectorNode::SelectorEdge(ctx.getManager(), tuple.first)); + s->getEdges().push_back(new model::SelectorNode::SelectorEdge( + ctx.getManager(), tuple.first)); // and we return this node as well as the leaf. return std::make_pair(s, tuple.second); } @@ -176,9 +177,9 @@ std::pair, Rooted> CSSParser::parseSelector( // so we parse the rest of the subsequent SelectorPath auto tuple = parseSelector(tokenizer, ctx); // then we establish the DESCENDANT relationship - s->getEdges().push_back(new SelectorNode::SelectorEdge( + s->getEdges().push_back(new model::SelectorNode::SelectorEdge( ctx.getManager(), tuple.first, - SelectionOperator::DIRECT_DESCENDANT)); + model::SelectionOperator::DIRECT_DESCENDANT)); // and we return this node as well as the leaf. return std::make_pair(s, tuple.second); } @@ -189,8 +190,8 @@ std::pair, Rooted> CSSParser::parseSelector( } } -Rooted CSSParser::parsePrimitiveSelector(CodeTokenizer &tokenizer, - ParserContext &ctx) +Rooted CSSParser::parsePrimitiveSelector( + CodeTokenizer &tokenizer, ParserContext &ctx) { // first and foremost we expect a class name. Token t; @@ -198,7 +199,8 @@ Rooted CSSParser::parsePrimitiveSelector(CodeTokenizer &tokenizer, const std::string name = t.content; if (!tokenizer.peek(t)) { // if we are at the end, we just return this selector with its name. - Rooted n{new SelectorNode(ctx.getManager(), name)}; + Rooted n{ + new model::SelectorNode(ctx.getManager(), name)}; return n; } @@ -218,8 +220,9 @@ Rooted CSSParser::parsePrimitiveSelector(CodeTokenizer &tokenizer, // look for additional arguments. if (!expect(PAREN_OPEN, tokenizer, t, false, ctx)) { // if we don't have any, we return here. - Rooted n{new SelectorNode( - ctx.getManager(), name, {pseudo_select_name, isGenerative})}; + Rooted n{ + new model::SelectorNode(ctx.getManager(), name, + {pseudo_select_name, isGenerative})}; return n; } // parse the argument list. @@ -227,18 +230,19 @@ Rooted CSSParser::parsePrimitiveSelector(CodeTokenizer &tokenizer, // we require at least one argument, if parantheses are used // XXX args.push_back(VariantReader::parseGeneric(tokenizer.getInput(), - ctx.getLogger(), - {',', ')'}).second); + ctx.getLogger(), + {',', ')'}).second); while (expect(COMMA, tokenizer, t, false, ctx)) { // as long as we find commas we expect new arguments. - args.push_back( - VariantReader::parseGeneric( - tokenizer.getInput(), ctx.getLogger(), {',', ')'}).second); + args.push_back(VariantReader::parseGeneric(tokenizer.getInput(), + ctx.getLogger(), + {',', ')'}).second); } expect(PAREN_CLOSE, tokenizer, t, true, ctx); // and we return with the finished Selector. - Rooted n{new SelectorNode( - ctx.getManager(), name, {pseudo_select_name, args, isGenerative})}; + Rooted n{ + new model::SelectorNode(ctx.getManager(), name, + {pseudo_select_name, args, isGenerative})}; return n; } case HASH: { @@ -249,8 +253,8 @@ Rooted CSSParser::parsePrimitiveSelector(CodeTokenizer &tokenizer, expect(TOKEN_TEXT, tokenizer, t, true, ctx); Variant::arrayType args{Variant(t.content.c_str())}; // and we return the finished Selector - Rooted n{ - new SelectorNode(ctx.getManager(), name, {"has_id", args, false})}; + Rooted n{new model::SelectorNode( + ctx.getManager(), name, {"has_id", args, false})}; return n; } case BRACKET_OPEN: { @@ -269,7 +273,7 @@ Rooted CSSParser::parsePrimitiveSelector(CodeTokenizer &tokenizer, // we expect a closing bracket. expect(BRACKET_CLOSE, tokenizer, t, true, ctx); // and then we can return the result. - Rooted n{new SelectorNode( + Rooted n{new model::SelectorNode( ctx.getManager(), name, {"has_attribute", args, false})}; return n; } else { @@ -280,7 +284,7 @@ Rooted CSSParser::parsePrimitiveSelector(CodeTokenizer &tokenizer, // then we expect a closing bracket. expect(BRACKET_CLOSE, tokenizer, t, true, ctx); // and then we can return the result. - Rooted n{new SelectorNode( + Rooted n{new model::SelectorNode( ctx.getManager(), name, {"has_value", args, false})}; return n; } @@ -288,15 +292,16 @@ Rooted CSSParser::parsePrimitiveSelector(CodeTokenizer &tokenizer, default: // everything else is not part of the Selector anymore. tokenizer.resetPeek(); - Rooted n{new SelectorNode(ctx.getManager(), name)}; + Rooted n{ + new model::SelectorNode(ctx.getManager(), name)}; return n; } } -Rooted CSSParser::parseRuleSet(CodeTokenizer &tokenizer, - ParserContext &ctx) +Rooted CSSParser::parseRuleSet(CodeTokenizer &tokenizer, + ParserContext &ctx) { - Rooted ruleSet{new RuleSet(ctx.getManager())}; + Rooted ruleSet{new model::RuleSet(ctx.getManager())}; // if we have no ruleset content, we return an empty ruleset. Token t; if (!expect(CURLY_OPEN, tokenizer, t, false, ctx)) { @@ -309,8 +314,8 @@ Rooted CSSParser::parseRuleSet(CodeTokenizer &tokenizer, return ruleSet; } -void CSSParser::parseRules(CodeTokenizer &tokenizer, Rooted ruleSet, - ParserContext &ctx) +void CSSParser::parseRules(CodeTokenizer &tokenizer, + Rooted ruleSet, ParserContext &ctx) { std::string key; Variant value; @@ -333,7 +338,7 @@ bool CSSParser::parseRule(CodeTokenizer &tokenizer, ParserContext &ctx, // then the value // TODO: Resolve key for appropriate parsing function here. value = VariantReader::parseGeneric(tokenizer.getInput(), ctx.getLogger(), - {';'}).second; + {';'}).second; // and a ; expect(SEMICOLON, tokenizer, t, true, ctx); return true; @@ -347,10 +352,10 @@ bool CSSParser::expect(int expectedType, CodeTokenizer &tokenizer, Token &t, if (force) { if (end) { throw LoggableException{"Unexpected end of file!", - tokenizer.getInput()}; + tokenizer.getInput()}; } else { throw LoggableException{"Unexpected token!", - tokenizer.getInput()}; + tokenizer.getInput()}; } } else { tokenizer.resetPeek(); diff --git a/src/plugins/css/CSSParser.hpp b/src/plugins/css/CSSParser.hpp index c6594f6..2f37e6a 100644 --- a/src/plugins/css/CSSParser.hpp +++ b/src/plugins/css/CSSParser.hpp @@ -32,8 +32,8 @@ #include #include -#include #include +#include #include namespace ousia { @@ -71,42 +71,44 @@ private: /** * Implements the DOC Nonterminal */ - void parseDocument(Rooted root, CodeTokenizer &tokenizer, - ParserContext &ctx); + void parseDocument(Rooted root, + CodeTokenizer &tokenizer, ParserContext &ctx); /** * Implements the SELECTORS Nonterminal and adds all leaf nodes of the * resulting SelectorTree to the input leafList so that a parsed RuleSet can * be inserted there. */ - void parseSelectors(Rooted root, CodeTokenizer &tokenizer, - std::vector> &leafList, + void parseSelectors(Rooted root, + CodeTokenizer &tokenizer, + std::vector> &leafList, ParserContext &ctx); /** * Implements the SELECT Nonterminal, which in effect parses a SelectorPath * of the SelectorTree and returns the beginning node of the path as first * element as well as the leaf of the path as second tuple element. */ - std::pair, Rooted> parseSelector( - CodeTokenizer &tokenizer, ParserContext &ctx); + std::pair, Rooted> + parseSelector(CodeTokenizer &tokenizer, ParserContext &ctx); /** * Implements the SELECT' Nonterminal, which parses a single Selector with * its PseudoSelector and returns it. */ - Rooted parsePrimitiveSelector(CodeTokenizer &tokenizer, - ParserContext &ctx); + Rooted parsePrimitiveSelector(CodeTokenizer &tokenizer, + ParserContext &ctx); /** * 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 parseRuleSet(CodeTokenizer &tokenizer, ParserContext &ctx); + Rooted parseRuleSet(CodeTokenizer &tokenizer, + ParserContext &ctx); /** * Implements the RULES Nonterminal, which parses CSSRules inside a RuleSet. */ - void parseRules(CodeTokenizer &tokenizer, Rooted ruleSet, + void parseRules(CodeTokenizer &tokenizer, Rooted ruleSet, ParserContext &ctx); /** -- cgit v1.2.3