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

test

parent f81c7d07
Loading
Loading
Loading
Loading
+66 −9
Changes for editor/src/htmlimport.cpp: 66 added lines, 9 removed lines.
Original line number Diff line number Diff line
@@ -505,6 +505,35 @@ void applyFlexLayoutAttributes(tinyxml2::XMLElement *w,
    }
}

// Merges this element's own --custom-property declarations onto the
// inherited environment (own values override inherited -- innermost scope
// wins, matching real CSS custom-property cascade/inheritance), resolves
// every var(...) reference in cssProps' values -- including the just-merged
// custom properties themselves, since e.g. "--gradient-bg" commonly
// references "--color-bg" -- via the resulting environment, then strips
// the -- entries out of cssProps: they have no widget-attribute or
// CustomCss meaning on their own, their only purpose was making other
// properties resolvable, and leaving them in would dump inert
// "--color-bg: 0,0,0;"-style text into CustomCss for no benefit. Returns
// the updated environment for this element's children to inherit.
std::map<std::string,std::string> resolveCustomProperties(
    std::map<std::string,std::string> &cssProps,
    const std::map<std::string,std::string> &inheritedEnv)
{
    std::map<std::string,std::string> env = inheritedEnv;
    for (const auto &kv : cssProps) {
        if (kv.first.compare(0, 2, "--") == 0) env[kv.first] = kv.second;
    }
    for (auto &kv : cssProps) {
        kv.second = libhtmlpp::resolveCSSVariables(kv.second, env);
    }
    for (auto it = cssProps.begin(); it != cssProps.end(); ) {
        if (it->first.compare(0, 2, "--") == 0) it = cssProps.erase(it);
        else ++it;
    }
    return env;
}

} // namespace

std::string blogi::htmlimport::extractCustomCss(
@@ -700,7 +729,8 @@ void blogi::htmlimport::htmlElementToWidgetXml(
    tinyxml2::XMLElement *parentXml,
    const libhtmlpp::CSSStyleSheet &sheet,
    std::set<std::string> &seenMediaBlocks,
    libhtmlpp::Element *stopBefore)
    libhtmlpp::Element *stopBefore,
    const std::map<std::string,std::string> &rootCustomProperties)
{
    if (!htmlEl) return;

@@ -710,9 +740,14 @@ void blogi::htmlimport::htmlElementToWidgetXml(
    // callers process a single node (at any depth) in isolation (see
    // convertHtmlToWidgetXml's selectedPaths handling) without mutating
    // the parsed document tree.
    struct Frame { libhtmlpp::Element *cur; tinyxml2::XMLElement *pXml; libhtmlpp::Element *stop = nullptr; };
    struct Frame {
        libhtmlpp::Element *cur;
        tinyxml2::XMLElement *pXml;
        libhtmlpp::Element *stop = nullptr;
        std::map<std::string,std::string> customPropertyEnv = {};
    };
    std::stack<Frame> workStack;
    workStack.push({htmlEl, parentXml, stopBefore});
    workStack.push({htmlEl, parentXml, stopBefore, rootCustomProperties});

    while (!workStack.empty()) {
        auto frame = workStack.top();
@@ -762,7 +797,9 @@ void blogi::htmlimport::htmlElementToWidgetXml(
            if (tag == "html" || tag == "body" || tag == "head") {
                libhtmlpp::Element *child = el->firstChild();
                if (child) {
                    childFrames.push_back({child, pXml});
                    // No cssProps computed for these structural tags -- just
                    // pass the inherited environment through unchanged.
                    childFrames.push_back({child, pXml, nullptr, frame.customPropertyEnv});
                }
                cur = cur->nextElement();
                continue;
@@ -784,6 +821,8 @@ void blogi::htmlimport::htmlElementToWidgetXml(
            std::string mediaRules = cssRules.mediaRules;
            applyBackgroundColorShorthand(cssProps);
            normalizeShorthandProperties(cssProps);
            std::map<std::string,std::string> elementEnv =
                resolveCustomProperties(cssProps, frame.customPropertyEnv);

            // ---- Map HTML tags to widget types ----

@@ -837,7 +876,7 @@ void blogi::htmlimport::htmlElementToWidgetXml(
                }
                pXml->InsertEndChild(w);
                libhtmlpp::Element *child = el->firstChild();
                if (child) childFrames.push_back({child, w});
                if (child) childFrames.push_back({child, w, nullptr, elementEnv});
            };

            if (tag == "section") {
@@ -880,7 +919,7 @@ void blogi::htmlimport::htmlElementToWidgetXml(
                }
                pXml->InsertEndChild(w);
                libhtmlpp::Element *child = el->firstChild();
                if (child) childFrames.push_back({child, w});
                if (child) childFrames.push_back({child, w, nullptr, elementEnv});

            } else if (tag == "div") {
                bool isGrid = cssProps.count("display") && cssProps["display"] == "grid";
@@ -952,7 +991,7 @@ void blogi::htmlimport::htmlElementToWidgetXml(
                }
                pXml->InsertEndChild(w);
                libhtmlpp::Element *child = el->firstChild();
                if (child) childFrames.push_back({child, w});
                if (child) childFrames.push_back({child, w, nullptr, elementEnv});

            } else if (tag == "img") {
                tinyxml2::XMLElement *w = doc.NewElement("Image");
@@ -1937,6 +1976,15 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html,

    // Extract global CSS rules
    std::string globalCss;
    // Base CSS custom-property ("--name") environment every element
    // inherits from, seeded from these same :root/html/body/*-scoped rules
    // -- see resolveCustomProperties, which threads this down per-element
    // via htmlElementToWidgetXml's Frame::customPropertyEnv. A rule
    // qualifying via an @media wrapper contributes unconditionally (media
    // conditions aren't evaluated anywhere in this file); a later
    // document-order rule for the same name simply overwrites an earlier
    // one, same "last one wins" approximation used elsewhere here.
    std::map<std::string,std::string> rootEnv;
    for (const auto &rule : stylesheet.getRules()) {
        const std::string &sel = rule.getSelector();
        if (sel.empty()) continue;
@@ -1990,6 +2038,14 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html,
            }
        }

        if (isGlobal) {
            for (const auto &prop : rule.getDeclaration().getProperties()) {
                if (prop.getName().compare(0, 2, "--") == 0) {
                    rootEnv[prop.getName()] = prop.getValue();
                }
            }
        }

        if (isGlobal && sel[0] == '@' && !innerSel.empty()) {
            // rule.serialize() just wraps getSelector() in a single "{ }"
            // pair -- fine for a plain selector, but here getSelector() is
@@ -2027,11 +2083,12 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html,
            for (const auto &path : *selectedPaths) {
                libhtmlpp::Element *el = resolveImportPath(root, path);
                if (el) {
                    htmlElementToWidgetXml(el, xmlDoc, xmlRoot, stylesheet, seenMediaBlocks, el->nextElement());
                    htmlElementToWidgetXml(el, xmlDoc, xmlRoot, stylesheet, seenMediaBlocks,
                                            el->nextElement(), rootEnv);
                }
            }
        } else {
            htmlElementToWidgetXml(root, xmlDoc, xmlRoot, stylesheet, seenMediaBlocks);
            htmlElementToWidgetXml(root, xmlDoc, xmlRoot, stylesheet, seenMediaBlocks, nullptr, rootEnv);
        }
    }

+13 −1
Changes for editor/src/htmlimport.h: 13 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -101,13 +101,25 @@ namespace htmlimport {
    std::string rewriteCssUrls(const std::string &css,
                                const std::function<std::string(const std::string&)> &fn);

    /** @p rootCustomProperties seeds the CSS custom-property ("--name")
     *  environment inherited by @p htmlEl itself -- e.g. any `:root`/`html`/
     *  `body`/`*`-scoped `--name: value;` declarations found document-wide
     *  (see convertHtmlToWidgetXml's "Extract global CSS rules" step, which
     *  already isolates these same rules for the GlobalCss text dump). Each
     *  element's own `--`-prefixed declarations are merged onto whatever
     *  it inherits (innermost wins, matching real CSS custom-property
     *  cascade/inheritance) and threaded down to its children -- see
     *  resolveCustomProperties in htmlimport.cpp. libhtmlpp::Element has no
     *  parent pointer, so this is threaded top-down through the traversal
     *  rather than looked up via ancestor walk. */
    void htmlElementToWidgetXml(
        libhtmlpp::Element *htmlEl,
        tinyxml2::XMLDocument &doc,
        tinyxml2::XMLElement *parentXml,
        const libhtmlpp::CSSStyleSheet &sheet,
        std::set<std::string> &seenMediaBlocks,
        libhtmlpp::Element *stopBefore = nullptr);
        libhtmlpp::Element *stopBefore = nullptr,
        const std::map<std::string,std::string> &rootCustomProperties = {});

    /** One node of the HTML-import block picker's tree: an element reachable
     *  from the document root by transparently unwrapping <html>/<body> and
+61 −0
Changes for tests/htmlimport_smoke.cpp: 61 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -270,6 +270,67 @@ int main(){
              "align_items is set alongside vertical_align and direction");
    }

    // --- CSS custom property (var()) resolution ---
    std::cout << "=== CSS custom property resolution ===" << std::endl;
    {
        // The real-world pattern found on a live import: inline style sets
        // --color-bg/--alpha-bg, a <style> block rule (matched by class)
        // consumes them via rgba(var(...),var(...)).
        std::string html =
            "<html><head><style>"
            ".hero { background-color: rgba(var(--color-bg),var(--alpha-bg)); }"
            "</style></head><body>"
            "<div class=\"hero\" style=\"--color-bg:0,0,0;--alpha-bg:1\">hi</div>"
            "</body></html>";
        std::string xml = importXml(html);
        check(xml.find("background_color=\"rgba(0,0,0,1)\"") != std::string::npos,
              "same-element --color-bg/--alpha-bg resolve through a matched rule's var()");
        check(xml.find("--color-bg") == std::string::npos,
              "the inert --color-bg/--alpha-bg declarations themselves don't leak into the output");
    }
    {
        // --color-bg set on a PARENT div, consumed by a CHILD's own matched
        // rule -- proves the Frame-threaded environment actually inherits
        // down the tree, not just merges within one element.
        std::string html =
            "<html><head><style>"
            ".child { background-color: rgba(var(--color-bg),1); }"
            "</style></head><body>"
            "<div style=\"--color-bg:10,20,30\">"
            "<div class=\"child\">hi</div>"
            "</div>"
            "</body></html>";
        std::string xml = importXml(html);
        check(xml.find("background_color=\"rgba(10,20,30,1)\"") != std::string::npos,
              "a custom property set on an ancestor is inherited by a descendant's matched rule");
    }
    {
        // :root-scoped custom property, no local override anywhere --
        // proves the base-environment seeding from convertHtmlToWidgetXml's
        // "Extract global CSS rules" step.
        std::string html =
            "<html><head><style>"
            ":root { --brand: #144; }"
            ".box { background-color: var(--brand); }"
            "</style></head><body>"
            "<div class=\"box\">hi</div>"
            "</body></html>";
        std::string xml = importXml(html);
        check(xml.find("background_color=\"#144\"") != std::string::npos,
              ":root-scoped custom property resolves via the seeded base environment");
    }
    {
        std::string html =
            "<html><head><style>"
            ".box { background-color: var(--undefined, blue); }"
            "</style></head><body>"
            "<div class=\"box\">hi</div>"
            "</body></html>";
        std::string xml = importXml(html);
        check(xml.find("background_color=\"blue\"") != std::string::npos,
              "an unset custom property falls back to var()'s fallback argument");
    }

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