/* 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 . */ #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