Commit 464005e5 authored by jan.koester's avatar jan.koester
Browse files

test

parent ab5647f5
Loading
Loading
Loading
Loading
+91 −5
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "exception.h"

#include <algorithm>
#include <array>
#include <cctype>
#include <sstream>

@@ -297,6 +298,72 @@ namespace {
        return std::string::npos;
    }

    // Approximate CSS specificity of one full comma-branch selector (e.g.
    // "div.card#hero[data-x]:hover .child"), as the standard (id-count,
    // class/attribute/pseudo-class-count, type/pseudo-element-count) triple
    // -- compared lexicographically, so any number of classes always loses
    // to a single id, any number of tags always loses to a single class,
    // same as a real browser's cascade. Used to let a more specific
    // matching rule win regardless of source order (see
    // collectApproximateMatches), instead of this file's usual plain
    // last-rule-wins approximation -- unlike matching itself, specificity
    // doesn't need per-element context, so it's computed once per rule
    // from the raw selector text and cached (see _CompoundCacheBranch),
    // covering compounds this matcher can't safely verify (attribute
    // selectors, pseudo-classes -- see stripAttributeSelectors/
    // hasUnsupportedSelectorSyntax) exactly the same as ones it can: real
    // CSS specificity is a property of the selector text, independent of
    // whether this approximate matcher happens to be able to verify every
    // part of it. Deliberately approximate like the rest of this file: a
    // literal '.'/'#'/'[' inside a quoted attribute value (e.g.
    // "[href=\"a.b\"]") could miscount since this scans raw text rather
    // than a real tokenizer, but attribute selectors are always skipped as
    // one atomic "[...]" unit (never scanned inside), so that specific case
    // doesn't actually miscount.
    std::array<int,3> computeSpecificity(const std::string &selector) {
        std::array<int,3> spec{0, 0, 0};
        size_t pos = 0;
        auto skipIdent = [&](size_t p) {
            while (p < selector.size() &&
                   (std::isalnum(static_cast<unsigned char>(selector[p])) ||
                    selector[p] == '-' || selector[p] == '_' || selector[p] == '\\')) {
                ++p;
            }
            return p;
        };
        while (pos < selector.size()) {
            char c = selector[pos];
            if (c == '#') {
                ++spec[0];
                pos = skipIdent(pos + 1);
            } else if (c == '.') {
                ++spec[1];
                pos = skipIdent(pos + 1);
            } else if (c == '[') {
                ++spec[1];
                size_t close = selector.find(']', pos + 1);
                pos = (close == std::string::npos) ? selector.size() : close + 1;
            } else if (c == ':') {
                bool pseudoElement = pos + 1 < selector.size() && selector[pos + 1] == ':';
                ++spec[pseudoElement ? 2 : 1];
                pos = skipIdent(pos + (pseudoElement ? 2 : 1));
                if (pos < selector.size() && selector[pos] == '(') {
                    size_t close = findMatchingParen(selector, pos);
                    pos = (close == std::string::npos) ? selector.size() : close + 1;
                }
            } else if (c == '*') {
                ++pos; // universal selector: contributes nothing, per spec
            } else if (std::isalpha(static_cast<unsigned char>(c)) || c == '_') {
                size_t next = skipIdent(pos);
                if (next > pos) ++spec[2];
                pos = next;
            } else {
                ++pos; // whitespace/combinators/etc -- not part of any token
            }
        }
        return spec;
    }

    // Splits `inner` (the text between "var(" and its matching ")") at the
    // first top-level comma (depth 0, not inside a nested "(...)") into a
    // trimmed custom-property name and a trimmed fallback ("" if no comma
@@ -886,6 +953,7 @@ void libhtmlpp::CSSStyleSheet::_rebuildCompoundCache() const {
                    branch.isAtRule = isAtRule;
                    branch.atWrapper = atWrapper;
                    branch.trimmedSelector = singleSel;
                    branch.specificity = computeSpecificity(singleSel);

                    std::vector<std::string> chain = splitCombinatorChain(singleSel);
                    bool hadCombinator = chain.size() > 1;
@@ -946,6 +1014,17 @@ void libhtmlpp::CSSStyleSheet::collectApproximateMatches(
    std::set<std::string> inlineKeys;
    for (const auto &kv : props) inlineKeys.insert(kv.first);
    std::set<std::string> importantKeys;
    // Specificity of whichever rule currently "owns" each property, kept
    // separately per precedence tier (plain vs !important) so a more
    // specific rule wins regardless of source order within its own tier --
    // real CSS cascade order is (origin/importance, specificity, source
    // order); a later-but-less-specific rule should never beat an earlier
    // one (confirmed on a real page-builder site: a plain
    // ".fade-box img{width:auto}" was winning over an earlier but more
    // specific ".fade-box[data-fit=fill] img{object-fit:cover}" purely
    // because it came later in the stylesheet).
    std::map<std::string, std::array<int,3>> plainSpecOf;
    std::map<std::string, std::array<int,3>> importantSpecOf;

    if (!_compoundCacheValid) _rebuildCompoundCache();

@@ -993,14 +1072,20 @@ void libhtmlpp::CSSStyleSheet::collectApproximateMatches(
                    const std::string &key = prop.getName();

                    // Inline style and an already-!important value both
                    // outrank a later plain rule; a later rule of equal
                    // priority (both plain, or both !important) wins --
                    // approximating the real cascade's "last rule of
                    // equal-or-higher precedence wins" without computing
                    // full selector specificity.
                    // outrank a later plain rule.
                    if (inlineKeys.count(key) && !isImportant) continue;
                    if (importantKeys.count(key) && !isImportant) continue;

                    // Within a precedence tier (plain vs !important, see
                    // above), a rule with LOWER specificity than whichever
                    // one currently owns this property loses -- same as a
                    // real cascade. Equal specificity still falls through
                    // to "later rule wins" below, matching real CSS.
                    auto &specOf = isImportant ? importantSpecOf : plainSpecOf;
                    auto specIt = specOf.find(key);
                    if (specIt != specOf.end() && specIt->second > branch.specificity)
                        continue;

                    // A match that only went through because an ancestor
                    // condition we can't verify (see
                    // ancestorChainSatisfies/usedUnverifiableAncestor) was
@@ -1017,6 +1102,7 @@ void libhtmlpp::CSSStyleSheet::collectApproximateMatches(
                        continue;

                    props[key] = value;
                    specOf[key] = branch.specificity;
                    if (isImportant) importantKeys.insert(key);
                }
            }
+9 −0
Original line number Diff line number Diff line
@@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <map>
#include <set>
#include <memory>
#include <array>

namespace libhtmlpp {

@@ -266,6 +267,14 @@ namespace libhtmlpp {
                std::string id;
            };
            std::vector<AncestorCompound> ancestorCompounds;
            // Approximate CSS specificity of this branch's full selector
            // text (trimmedSelector), as the standard (id-count,
            // class/attribute/pseudo-class-count, type/pseudo-element-count)
            // triple -- see computeSpecificity in css.cpp. Used by
            // collectApproximateMatches to let a more specific matching rule
            // win regardless of source order, instead of the plain
            // last-rule-wins approximation this file used everywhere else.
            std::array<int,3> specificity{0,0,0};
        };
        mutable std::vector<std::vector<_CompoundCacheBranch>> _compoundCache;
        mutable bool _compoundCacheValid = false;