Loading editor/src/htmlimport.cpp +103 −0 Changes for editor/src/htmlimport.cpp: 103 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -273,6 +273,85 @@ bool stripImportant(std::string &value) return true; } // Best-effort extraction of just the color component from a `background` // shorthand value (e.g. "background: url(x.png) no-repeat center red;") -- // every widget's known-CSS mapping only ever looks for the longhand // "background-color" key (see the cssProps.count("background-color") // call sites in htmlElementToWidgetXml), never "background" itself, so a // page that only ever wrote the shorthand previously left the widget's own // dedicated background_color attribute unset. That matters beyond cosmetics: // an element with neither class nor id has no selector to hang the raw // shorthand text on via extractCustomCss (see its own doc comment), so the // color was silently dropped from the rendered page entirely, not just // demoted to a secondary mechanism -- a common case for scraped markup with // unstyled wrapper divs. Returns "" (derive nothing) whenever more than one // token could plausibly be the color -- guessing wrong is worse than not // filling in the shortcut attribute, and the full shorthand text is always // preserved regardless (when the element does have a class/id) so nothing // already-working regresses. std::string extractBackgroundColorFromShorthand(const std::string &value) { static const std::set<std::string> kNonColorKeywords = { "none", "repeat", "repeat-x", "repeat-y", "no-repeat", "space", "round", "fixed", "local", "scroll", "border-box", "padding-box", "content-box", "cover", "contain", "top", "bottom", "left", "right", "center", "inherit", "initial", "unset", "revert", }; // Tokenize on whitespace, but keep a "url(...)"/"rgb(...)"/gradient(...) // -style function call together as one token even when its arguments // contain spaces (e.g. "rgba(0, 0, 0, .5)"). std::vector<std::string> tokens; size_t i = 0, n = value.size(); while (i < n) { while (i < n && std::isspace(static_cast<unsigned char>(value[i]))) ++i; if (i >= n) break; size_t start = i; int depth = 0; while (i < n && (depth > 0 || !std::isspace(static_cast<unsigned char>(value[i])))) { if (value[i] == '(') ++depth; else if (value[i] == ')') { if (depth > 0) --depth; } ++i; } tokens.push_back(value.substr(start, i - start)); } std::string candidate; int candidateCount = 0; for (const auto &tok : tokens) { if (tok.empty()) continue; std::string lower = tok; std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return std::tolower(c); }); bool looksLikeColor = tok[0] == '#' || lower.compare(0, 4, "rgb(") == 0 || lower.compare(0, 5, "rgba(") == 0 || lower.compare(0, 4, "hsl(") == 0 || lower.compare(0, 5, "hsla(") == 0 || lower.compare(0, 4, "var(") == 0; if (!looksLikeColor) { // Any other function call (url(...), a gradient, image-set(...), // ...) is definitely not a plain color -- skip without counting // it as an (ambiguous) candidate. if (tok.find('(') != std::string::npos) continue; if (kNonColorKeywords.find(lower) != kNonColorKeywords.end()) continue; // A length/percentage/position-size piece ("50%", "10px", // "center/cover") always contains one of these characters; // a bare word that doesn't is most plausibly a named color // (e.g. "red", "transparent") -- this file's usual best-effort // spirit rather than matching all ~150 CSS color keywords. if (tok.find_first_of("0123456789%/") != std::string::npos) continue; looksLikeColor = true; } candidate = tok; candidateCount++; } return candidateCount == 1 ? candidate : std::string(); } } // namespace std::map<std::string,std::string> Loading @@ -285,6 +364,12 @@ blogi::htmlimport::declarationToMap( stripImportant(value); result[prop.getName()] = value; } if (result.count("background") && !result.count("background-color")) { std::string bg = extractBackgroundColorFromShorthand(result["background"]); if (!bg.empty()) result["background-color"] = bg; } return result; } Loading Loading @@ -480,6 +565,24 @@ void blogi::htmlimport::mergeStylesheetRules( props[key] = value; if (isImportant) importantKeys.insert(key); // See extractBackgroundColorFromShorthand's doc // comment -- widgets only ever look for the // longhand "background-color", never "background" // itself, so derive it here too under the exact // same priority rules as any other property match. if (key == "background") { std::string bg = extractBackgroundColorFromShorthand(value); if (!bg.empty()) { const std::string bgKey = "background-color"; bool skip = (inlineKeys.count(bgKey) && !isImportant) || (importantKeys.count(bgKey) && !isImportant); if (!skip) { props[bgKey] = bg; if (isImportant) importantKeys.insert(bgKey); } } } } } } Loading Loading
editor/src/htmlimport.cpp +103 −0 Changes for editor/src/htmlimport.cpp: 103 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -273,6 +273,85 @@ bool stripImportant(std::string &value) return true; } // Best-effort extraction of just the color component from a `background` // shorthand value (e.g. "background: url(x.png) no-repeat center red;") -- // every widget's known-CSS mapping only ever looks for the longhand // "background-color" key (see the cssProps.count("background-color") // call sites in htmlElementToWidgetXml), never "background" itself, so a // page that only ever wrote the shorthand previously left the widget's own // dedicated background_color attribute unset. That matters beyond cosmetics: // an element with neither class nor id has no selector to hang the raw // shorthand text on via extractCustomCss (see its own doc comment), so the // color was silently dropped from the rendered page entirely, not just // demoted to a secondary mechanism -- a common case for scraped markup with // unstyled wrapper divs. Returns "" (derive nothing) whenever more than one // token could plausibly be the color -- guessing wrong is worse than not // filling in the shortcut attribute, and the full shorthand text is always // preserved regardless (when the element does have a class/id) so nothing // already-working regresses. std::string extractBackgroundColorFromShorthand(const std::string &value) { static const std::set<std::string> kNonColorKeywords = { "none", "repeat", "repeat-x", "repeat-y", "no-repeat", "space", "round", "fixed", "local", "scroll", "border-box", "padding-box", "content-box", "cover", "contain", "top", "bottom", "left", "right", "center", "inherit", "initial", "unset", "revert", }; // Tokenize on whitespace, but keep a "url(...)"/"rgb(...)"/gradient(...) // -style function call together as one token even when its arguments // contain spaces (e.g. "rgba(0, 0, 0, .5)"). std::vector<std::string> tokens; size_t i = 0, n = value.size(); while (i < n) { while (i < n && std::isspace(static_cast<unsigned char>(value[i]))) ++i; if (i >= n) break; size_t start = i; int depth = 0; while (i < n && (depth > 0 || !std::isspace(static_cast<unsigned char>(value[i])))) { if (value[i] == '(') ++depth; else if (value[i] == ')') { if (depth > 0) --depth; } ++i; } tokens.push_back(value.substr(start, i - start)); } std::string candidate; int candidateCount = 0; for (const auto &tok : tokens) { if (tok.empty()) continue; std::string lower = tok; std::transform(lower.begin(), lower.end(), lower.begin(), [](unsigned char c) { return std::tolower(c); }); bool looksLikeColor = tok[0] == '#' || lower.compare(0, 4, "rgb(") == 0 || lower.compare(0, 5, "rgba(") == 0 || lower.compare(0, 4, "hsl(") == 0 || lower.compare(0, 5, "hsla(") == 0 || lower.compare(0, 4, "var(") == 0; if (!looksLikeColor) { // Any other function call (url(...), a gradient, image-set(...), // ...) is definitely not a plain color -- skip without counting // it as an (ambiguous) candidate. if (tok.find('(') != std::string::npos) continue; if (kNonColorKeywords.find(lower) != kNonColorKeywords.end()) continue; // A length/percentage/position-size piece ("50%", "10px", // "center/cover") always contains one of these characters; // a bare word that doesn't is most plausibly a named color // (e.g. "red", "transparent") -- this file's usual best-effort // spirit rather than matching all ~150 CSS color keywords. if (tok.find_first_of("0123456789%/") != std::string::npos) continue; looksLikeColor = true; } candidate = tok; candidateCount++; } return candidateCount == 1 ? candidate : std::string(); } } // namespace std::map<std::string,std::string> Loading @@ -285,6 +364,12 @@ blogi::htmlimport::declarationToMap( stripImportant(value); result[prop.getName()] = value; } if (result.count("background") && !result.count("background-color")) { std::string bg = extractBackgroundColorFromShorthand(result["background"]); if (!bg.empty()) result["background-color"] = bg; } return result; } Loading Loading @@ -480,6 +565,24 @@ void blogi::htmlimport::mergeStylesheetRules( props[key] = value; if (isImportant) importantKeys.insert(key); // See extractBackgroundColorFromShorthand's doc // comment -- widgets only ever look for the // longhand "background-color", never "background" // itself, so derive it here too under the exact // same priority rules as any other property match. if (key == "background") { std::string bg = extractBackgroundColorFromShorthand(value); if (!bg.empty()) { const std::string bgKey = "background-color"; bool skip = (inlineKeys.count(bgKey) && !isImportant) || (importantKeys.count(bgKey) && !isImportant); if (!skip) { props[bgKey] = bg; if (isImportant) importantKeys.insert(bgKey); } } } } } } Loading