Commit 6a31a5e0 authored by jan.koester's avatar jan.koester
Browse files

test

parent 0f1988b9
Loading
Loading
Loading
Loading
+92 −0
Original line number Diff line number Diff line
@@ -163,6 +163,91 @@ namespace {
               matchSel == "*";
    }

    // Finds the index of the ')' matching the '(' at `openPos` (which must
    // point at '(' itself), respecting nested parens. Returns npos if
    // unterminated.
    size_t findMatchingParen(const std::string &s, size_t openPos) {
        int depth = 0;
        for (size_t i = openPos; i < s.size(); ++i) {
            if (s[i] == '(') ++depth;
            else if (s[i] == ')') {
                --depth;
                if (depth == 0) return i;
            }
        }
        return std::string::npos;
    }

    // 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
    // is present, i.e. no fallback was given).
    void splitVarArgs(const std::string &inner, std::string &name, std::string &fallback) {
        int depth = 0;
        size_t commaPos = std::string::npos;
        for (size_t i = 0; i < inner.size(); ++i) {
            if (inner[i] == '(') ++depth;
            else if (inner[i] == ')') --depth;
            else if (inner[i] == ',' && depth == 0) { commaPos = i; break; }
        }
        std::string rawName = (commaPos == std::string::npos) ? inner : inner.substr(0, commaPos);
        std::string rawFallback = (commaPos == std::string::npos) ? "" : inner.substr(commaPos + 1);
        name = trim(rawName);
        fallback = trim(rawFallback);
    }

    // Substitutes every var(...) occurrence in `value`, recursively --
    // `resolving` (the set of custom-property names currently being
    // expanded on this call stack) provides cycle detection: a name already
    // in it resolves to "" instead of recursing forever.
    std::string substituteVars(const std::string &value,
                                const std::map<std::string,std::string> &customProperties,
                                std::set<std::string> &resolving)
    {
        if (value.find("var(") == std::string::npos) return value;

        std::string out;
        out.reserve(value.size());
        size_t pos = 0;
        while (pos < value.size()) {
            if (value.compare(pos, 4, "var(") == 0) {
                size_t openParen = pos + 3;
                size_t closeParen = findMatchingParen(value, openParen);
                if (closeParen == std::string::npos) {
                    // Malformed/unterminated var( -- leave the rest as-is
                    // rather than risk corrupting it.
                    out += value.substr(pos);
                    break;
                }
                std::string inner = value.substr(openParen + 1, closeParen - openParen - 1);
                std::string name, fallback;
                splitVarArgs(inner, name, fallback);

                std::string substitution;
                if (resolving.count(name)) {
                    substitution = ""; // cyclic reference -- invalid per spec
                } else {
                    auto it = customProperties.find(name);
                    if (it != customProperties.end()) {
                        resolving.insert(name);
                        substitution = substituteVars(it->second, customProperties, resolving);
                        resolving.erase(name);
                    } else if (!fallback.empty()) {
                        substitution = substituteVars(fallback, customProperties, resolving);
                    } else {
                        substitution = ""; // unresolvable, no fallback -- invalid per spec
                    }
                }
                out += substitution;
                pos = closeParen + 1;
            } else {
                out += value[pos];
                ++pos;
            }
        }
        return out;
    }

}

// --- CSSProperty ---
@@ -669,3 +754,10 @@ void libhtmlpp::CSSStyleSheet::collectApproximateMatches(
    }
}

std::string libhtmlpp::resolveCSSVariables(const std::string &value,
                                             const std::map<std::string,std::string> &customProperties)
{
    std::set<std::string> resolving;
    return substituteVars(value, customProperties, resolving);
}
+19 −0
Original line number Diff line number Diff line
@@ -183,5 +183,24 @@ namespace libhtmlpp {
        std::vector<CSSRule> _Rules;
    };

    /** Resolves every `var(--name)` / `var(--name, fallback)` reference in
     *  @p value using @p customProperties (custom-property name -> its own
     *  raw stored value, e.g. from parsing "--color-bg: 0,0,0;"),
     *  recursively -- a custom property's own stored value may itself
     *  reference other custom properties (e.g. "--gradient-bg:
     *  var(--something)") -- with cycle detection: a reference cycle is
     *  invalid per the CSS Custom Properties spec, and resolves to ""
     *  rather than looping forever. A referenced name that's both absent
     *  from @p customProperties and has no fallback also resolves to ""
     *  (invalid, matching spec behavior for an unresolvable var()). A
     *  value containing no "var(" at all is returned unchanged (the
     *  overwhelmingly common case). This is pure text substitution -- the
     *  stored value of a custom property is inserted verbatim at the
     *  `var(...)` call's position, exactly as a browser's cascade would;
     *  no CSS value-type awareness (color/length/etc.) is needed or
     *  applied. */
    std::string resolveCSSVariables(const std::string &value,
                                     const std::map<std::string,std::string> &customProperties);

}
 
+47 −0
Original line number Diff line number Diff line
@@ -205,6 +205,53 @@ int main(){
        }
    }

    // --- resolveCSSVariables ---
    std::cout << "=== resolveCSSVariables ===" << std::endl;
    {
        std::map<std::string,std::string> props = {{"--color-bg", "0,0,0"}, {"--alpha-bg", "1"}};
        check(libhtmlpp::resolveCSSVariables("rgba(var(--color-bg),var(--alpha-bg))", props) ==
              "rgba(0,0,0,1)",
              "simple substitution, multiple var() in one value");
    }
    {
        std::map<std::string,std::string> props;
        check(libhtmlpp::resolveCSSVariables("var(--undefined, blue)", props) == "blue",
              "fallback used when the referenced name is unset");
    }
    {
        std::map<std::string,std::string> props;
        check(libhtmlpp::resolveCSSVariables("var(--undefined)", props) == "",
              "no fallback and unset name resolves to empty (invalid, per spec)");
    }
    {
        // Mirrors the real-world pattern: --gradient-bg's OWN stored value
        // references --color-bg/--alpha-bg (multi-hop resolution).
        std::map<std::string,std::string> props = {
            {"--color-bg", "10,20,30"},
            {"--alpha-bg", "0.5"},
            {"--gradient-bg", "rgba(var(--color-bg),var(--alpha-bg))"},
        };
        check(libhtmlpp::resolveCSSVariables("var(--gradient-bg)", props) == "rgba(10,20,30,0.5)",
              "multi-hop resolution through an intermediate custom property");
    }
    {
        // Fallback itself contains a nested var() -- also resolved.
        std::map<std::string,std::string> props = {{"--color-bg", "0,0,0"}};
        check(libhtmlpp::resolveCSSVariables(
                  "var(--gradient-bg, rgba(var(--color-bg),1))", props) == "rgba(0,0,0,1)",
              "nested var() inside a fallback value is resolved too");
    }
    {
        // a -> var(b), b -> var(a): cyclic, must not infinite-loop.
        std::map<std::string,std::string> props = {{"--a", "var(--b)"}, {"--b", "var(--a)"}};
        check(libhtmlpp::resolveCSSVariables("var(--a)", props) == "",
              "cyclic custom-property reference resolves to empty instead of looping forever");
    }
    {
        check(libhtmlpp::resolveCSSVariables("10px solid red", {}) == "10px solid red",
              "a value with no var( at all is returned unchanged");
    }

    // --- Summary ---
    std::cout << "\n=== " << passCount << "/" << testCount << " tests passed ===" << std::endl;