Loading editor/src/htmlimport.cpp +66 −9 Changes for editor/src/htmlimport.cpp: 66 added lines, 9 removed lines. Original line number Diff line number Diff line Loading @@ -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( Loading Loading @@ -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; Loading @@ -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(); Loading Loading @@ -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; Loading @@ -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 ---- Loading Loading @@ -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") { Loading Loading @@ -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"; Loading Loading @@ -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"); Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); } } Loading editor/src/htmlimport.h +13 −1 Changes for editor/src/htmlimport.h: 13 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -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 Loading tests/htmlimport_smoke.cpp +61 −0 Changes for tests/htmlimport_smoke.cpp: 61 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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; Loading Loading
editor/src/htmlimport.cpp +66 −9 Changes for editor/src/htmlimport.cpp: 66 added lines, 9 removed lines. Original line number Diff line number Diff line Loading @@ -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( Loading Loading @@ -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; Loading @@ -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(); Loading Loading @@ -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; Loading @@ -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 ---- Loading Loading @@ -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") { Loading Loading @@ -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"; Loading Loading @@ -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"); Loading Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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); } } Loading
editor/src/htmlimport.h +13 −1 Changes for editor/src/htmlimport.h: 13 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -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 Loading
tests/htmlimport_smoke.cpp +61 −0 Changes for tests/htmlimport_smoke.cpp: 61 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -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; Loading