Commit 767a76a4 authored by jan.koester's avatar jan.koester
Browse files

test

parent 2f4cc8f6
Loading
Loading
Loading
Loading
+103 −0
Changes for editor/src/htmlimport.cpp: 103 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -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>
@@ -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;
}

@@ -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);
                                }
                            }
                        }
                    }
                }
            }