Commit d43cfa4f authored by jan.koester's avatar jan.koester
Browse files

test

parent 4e0bd3df
Loading
Loading
Loading
Loading
+41 −26
Original line number Diff line number Diff line
@@ -285,30 +285,34 @@ namespace {
    // farther out than one an earlier compound already matched --
    // approximating real left-to-right descendant-selector semantics
    // without distinguishing combinator type, requiring adjacency, or
    // computing true specificity. An empty compound (unsafe syntax reduced
    // to nothing, see splitCombinatorChain's callers) is vacuously satisfied
    // without consuming an ancestor slot -- an unverifiable requirement
    // shouldn't by itself invalidate an otherwise plausible match, same
    // "not proven to not match" philosophy as the rest of this file. If @p
    // usedUnverifiableAncestor is non-null, it's set to true whenever this
    // happens, so a caller merging declarations into a props map (see
    // collectApproximateMatches) can single out display:none/
    // visibility:hidden from such a match -- unlike most properties, a wrong
    // guess there doesn't just mis-style an element, it makes the element
    // and its whole subtree disappear from the import (confirmed on a real
    // page-builder site: literally every spacer div was hidden because its
    // visibility rule's ancestor condition was an attribute selector like
    // "[data-mode=horizontal]", which this matcher can't verify and so
    // reduces to an empty, vacuously-satisfied compound -- see
    // stripAttributeSelectors).
    // computing true specificity. A compound with NEITHER tag/class/id NOR
    // any attribute condition (unsafe syntax reduced to nothing, see
    // splitCombinatorChain's callers -- an unsupported pseudo-class was
    // present) is vacuously satisfied without consuming an ancestor slot --
    // an unverifiable requirement shouldn't by itself invalidate an
    // otherwise plausible match, same "not proven to not match" philosophy
    // as the rest of this file. If @p usedUnverifiableAncestor is non-null,
    // it's set to true whenever this happens, so a caller merging
    // declarations into a props map (see collectApproximateMatches) can
    // single out display:none/visibility:hidden from such a match -- unlike
    // most properties, a wrong guess there doesn't just mis-style an
    // element, it makes the element and its whole subtree disappear from
    // the import. A compound that DOES carry attribute conditions (e.g.
    // "[data-fit=fill]" in ".fade-box[data-fit=fill] img") is a real,
    // checkable requirement: it's matched against each candidate frame's own
    // attributes (see AncestorFrame::attributes) the same way
    // compoundPartsMatch checks tag/class/id -- a frame with no attributes
    // supplied (an empty map, meaning the caller didn't populate that field)
    // can't refute it, so it's treated as satisfied for that frame rather
    // than guessed wrong.
    bool ancestorChainSatisfies(const std::vector<CompoundParts> &ancestorCompounds,
                                 const std::vector<libhtmlpp::AncestorFrame> &ancestors,
                                 bool *usedUnverifiableAncestor = nullptr)
    {
        size_t idx = 0;
        for (const auto &compound : ancestorCompounds) {
            bool specifiedSomething =
                !compound.tag.empty() || !compound.classes.empty() || !compound.id.empty();
            bool specifiedSomething = !compound.tag.empty() || !compound.classes.empty() ||
                                       !compound.id.empty() || !compound.attrConditions.empty();
            if (!specifiedSomething) {
                if (usedUnverifiableAncestor) *usedUnverifiableAncestor = true;
                continue;
@@ -318,7 +322,9 @@ namespace {
            while (idx < ancestors.size()) {
                const libhtmlpp::AncestorFrame &frame = ancestors[idx];
                ++idx;
                if (compoundPartsMatch(compound, frame.tag, frame.classes, frame.id)) {
                if (compoundPartsMatch(compound, frame.tag, frame.classes, frame.id) &&
                    attributeConditionsSatisfied(compound.attrConditions,
                        frame.attributes.empty() ? nullptr : &frame.attributes)) {
                    found = true;
                    break;
                }
@@ -955,8 +961,13 @@ bool libhtmlpp::CSSStyleSheet::approximateSelectorMatch(const std::string &selec
            std::vector<CompoundParts> ancestorCompounds;
            for (size_t i = 0; i + 1 < chain.size(); ++i) {
                std::string aSel = stripAttributeSelectors(chain[i]);
                ancestorCompounds.push_back(
                    hasUnsupportedSelectorSyntax(aSel) ? CompoundParts{} : parseCompoundSelector(aSel));
                if (hasUnsupportedSelectorSyntax(aSel)) {
                    ancestorCompounds.push_back({});
                } else {
                    CompoundParts aCompound = parseCompoundSelector(aSel);
                    aCompound.attrConditions = parseAttributeConditions(chain[i]);
                    ancestorCompounds.push_back(std::move(aCompound));
                }
            }
            if (!ancestorChainSatisfies(ancestorCompounds, *ancestors)) continue;
        }
@@ -1064,10 +1075,14 @@ void libhtmlpp::CSSStyleSheet::_rebuildCompoundCache() const {
                        branch.hadCombinator = hadCombinator;
                        for (size_t i = 0; i + 1 < chain.size(); ++i) {
                            std::string aSel = stripAttributeSelectors(chain[i]);
                            CompoundParts aCompound =
                                hasUnsupportedSelectorSyntax(aSel) ? CompoundParts{} : parseCompoundSelector(aSel);
                            if (hasUnsupportedSelectorSyntax(aSel)) {
                                branch.ancestorCompounds.push_back({});
                            } else {
                                CompoundParts aCompound = parseCompoundSelector(aSel);
                                branch.ancestorCompounds.push_back(
                                {aCompound.tag, aCompound.classes, aCompound.id});
                                    {aCompound.tag, aCompound.classes, aCompound.id,
                                     parseAttributeConditions(chain[i])});
                            }
                        }
                    }
                    branches.push_back(std::move(branch));
@@ -1142,7 +1157,7 @@ void libhtmlpp::CSSStyleSheet::collectApproximateMatches(
            if (ancestors && !branch.ancestorCompounds.empty()) {
                std::vector<CompoundParts> ancestorCompounds;
                for (const auto &ac : branch.ancestorCompounds) {
                    ancestorCompounds.push_back({ac.tag, ac.classes, ac.id});
                    ancestorCompounds.push_back({ac.tag, ac.classes, ac.id, ac.attrConditions});
                }
                if (!ancestorChainSatisfies(ancestorCompounds, *ancestors, &usedUnverifiableAncestor))
                    continue;
+20 −6
Original line number Diff line number Diff line
@@ -113,11 +113,20 @@ namespace libhtmlpp {
     *  Only presence/order in the chain is checked, not adjacency or
     *  combinator type: this remains an approximation of real CSS descendant
     *  matching, just a considerably safer one than ignoring ancestors
     *  entirely. */
     *  entirely. @p attributes (name -> value, optional -- an empty map
     *  means "not supplied", not "this ancestor has no attributes") lets an
     *  attribute-selector condition on an ANCESTOR compound (e.g. the
     *  "[data-fit=fill]" in ".fade-box[data-fit=fill] img") be verified the
     *  same way collectApproximateMatches's @p targetAttributes verifies one
     *  on the target compound -- a caller that doesn't populate this for its
     *  ancestor frames gets the old "ignore it" behavior for THOSE
     *  attribute-only ancestor conditions specifically (a tag/class/id
     *  requirement in the same compound is still checked as before). */
    struct AncestorFrame {
        std::string tag;
        std::vector<std::string> classes;
        std::string id;
        std::map<std::string,std::string> attributes;
    };

    /** One "[name]"/"[name=value]"/"[name~=value]"/etc. attribute-selector
@@ -289,15 +298,20 @@ namespace libhtmlpp {
            // Every compound left of the target one above, left-to-right/
            // outermost-first (e.g. for ".a .b .c", this holds ".a" and ".b"
            // -- "c" is the tag/classes/id/hadCombinator fields already
            // above). Empty tag/classes/id within one entry means that
            // compound's syntax couldn't be safely evaluated (same
            // attribute-selector/pseudo-class handling as the target) --
            // ancestorChainSatisfies treats that as vacuously satisfied
            // rather than an unmet requirement.
            // above). Empty tag/classes/id/attrConditions within one entry
            // means that compound's syntax couldn't be safely evaluated at
            // all (an unsupported pseudo-class was present -- see
            // hasUnsupportedSelectorSyntax) -- ancestorChainSatisfies treats
            // that as vacuously satisfied rather than an unmet requirement.
            // A compound with ONLY attrConditions (no tag/class/id, e.g.
            // "[data-fit=fill]") is a real, checkable requirement now that
            // AncestorFrame can carry an ancestor's own attributes -- see
            // ancestorChainSatisfies.
            struct AncestorCompound {
                std::string tag;
                std::vector<std::string> classes;
                std::string id;
                std::vector<AttributeCondition> attrConditions;
            };
            std::vector<AncestorCompound> ancestorCompounds;
            // Approximate CSS specificity of this branch's full selector