Loading editor/src/htmlimport.cpp +110 −40 Changes for editor/src/htmlimport.cpp: 110 added lines, 40 removed lines. Original line number Diff line number Diff line Loading @@ -437,6 +437,78 @@ std::string blogi::htmlimport::serializeInnerHtml(libhtmlpp::HtmlElement *el) { return out.str(); } std::string blogi::htmlimport::rewriteCssUrls( const std::string &css, const std::function<std::string(const std::string&)> &fn) { auto ciMatch = [&](size_t pos, const char *lit) { size_t len = std::strlen(lit); if (pos + len > css.size()) return false; for (size_t k = 0; k < len; ++k) { if (std::tolower(static_cast<unsigned char>(css[pos + k])) != std::tolower(static_cast<unsigned char>(lit[k]))) return false; } return true; }; std::string out; out.reserve(css.size()); size_t i = 0, n = css.size(); while (i < n) { if (ciMatch(i, "url(")) { size_t close = css.find(')', i + 4); if (close == std::string::npos) { out += css[i]; ++i; continue; } std::string inner = css.substr(i + 4, close - (i + 4)); size_t start = inner.find_first_not_of(" \t\r\n"); size_t end = inner.find_last_not_of(" \t\r\n"); std::string ref = (start == std::string::npos) ? "" : inner.substr(start, end - start + 1); char quote = '\0'; if (ref.size() >= 2 && (ref.front() == '"' || ref.front() == '\'') && ref.back() == ref.front()) { quote = ref.front(); ref = ref.substr(1, ref.size() - 2); } out += "url("; if (quote) out += quote; out += fn(ref); if (quote) out += quote; out += ')'; i = close + 1; } else if (ciMatch(i, "@import")) { out += css.substr(i, 7); i += 7; size_t wsStart = i; while (i < n && std::isspace(static_cast<unsigned char>(css[i]))) ++i; out += css.substr(wsStart, i - wsStart); // Bare-string form, e.g. @import "foo.css"; -- the url(...) form // (@import url(foo.css);) is handled by the branch above instead, // on a later pass through this same loop. if (i < n && (css[i] == '"' || css[i] == '\'')) { char q = css[i]; size_t strEnd = css.find(q, i + 1); if (strEnd != std::string::npos) { std::string ref = css.substr(i + 1, strEnd - (i + 1)); out += q; out += fn(ref); out += q; i = strEnd + 1; continue; } } } else { out += css[i]; ++i; } } return out; } void blogi::htmlimport::htmlElementToWidgetXml( libhtmlpp::Element *htmlEl, tinyxml2::XMLDocument &doc, Loading Loading @@ -1050,49 +1122,36 @@ std::string resolveUrl(const std::string &baseUrl, const std::string &ref) return urlOrigin(base) + path; } // Rewrites every url(...) reference found anywhere in @p css (declaration // values -- background/background-image/border-image/list-style-image/mask/ // cursor/@font-face src/... all accept it, so this scans the raw text // instead of enumerating property names) to absolute, using resolveUrl. // Quoted ("...", '...') and unquoted forms are both handled; whatever quoting // (or lack of it) the original used is preserved. Malformed/unterminated // url(...) is left untouched rather than risk corrupting the stylesheet. std::string rewriteCssUrls(const std::string &css, const std::string &baseUrl) // Resolves each URL in an <img>/<source> srcset="..." attribute -- a comma- // separated list of "<url> [descriptor]" candidates (descriptor being a // width like "480w" or pixel density like "2x") -- against baseUrl, leaving // each candidate's descriptor untouched. Doesn't handle a URL that itself // contains a literal comma (the HTML spec's own parsing algorithm for this // attribute is itself just a best-effort heuristic around that same case). std::string resolveSrcset(const std::string &baseUrl, const std::string &srcset) { std::string out; out.reserve(css.size()); size_t i = 0, n = css.size(); while (i < n) { if (i + 4 <= n && std::tolower(static_cast<unsigned char>(css[i])) == 'u' && std::tolower(static_cast<unsigned char>(css[i + 1])) == 'r' && std::tolower(static_cast<unsigned char>(css[i + 2])) == 'l' && css[i + 3] == '(') { size_t close = css.find(')', i + 4); if (close == std::string::npos) { out += css[i]; ++i; continue; } size_t start = 0; while (start <= srcset.size()) { size_t comma = srcset.find(',', start); std::string candidate = srcset.substr(start, comma == std::string::npos ? std::string::npos : comma - start); std::string inner = css.substr(i + 4, close - (i + 4)); size_t start = inner.find_first_not_of(" \t\r\n"); size_t end = inner.find_last_not_of(" \t\r\n"); std::string ref = (start == std::string::npos) ? "" : inner.substr(start, end - start + 1); size_t s = candidate.find_first_not_of(" \t\r\n"); size_t e = candidate.find_last_not_of(" \t\r\n"); if (s != std::string::npos) { candidate = candidate.substr(s, e - s + 1); size_t ws = candidate.find_first_of(" \t\r\n"); std::string url = (ws == std::string::npos) ? candidate : candidate.substr(0, ws); std::string descriptor = (ws == std::string::npos) ? "" : candidate.substr(ws); char quote = '\0'; if (ref.size() >= 2 && (ref.front() == '"' || ref.front() == '\'') && ref.back() == ref.front()) { quote = ref.front(); ref = ref.substr(1, ref.size() - 2); if (!out.empty()) out += ", "; out += resolveUrl(baseUrl, url); out += descriptor; } out += "url("; if (quote) out += quote; out += resolveUrl(baseUrl, ref); if (quote) out += quote; out += ')'; i = close + 1; } else { out += css[i]; ++i; } if (comma == std::string::npos) break; start = comma + 1; } return out; } Loading Loading @@ -1130,12 +1189,19 @@ void rewriteRelativeUrls(libhtmlpp::Element *node, const std::string &baseUrl) std::string href = hel->getAtributte("href"); if (!href.empty()) hel->setAttribute("href", resolveUrl(baseUrl, href)); } if (tag == "img" || tag == "source") { std::string srcset = hel->getAtributte("srcset"); if (!srcset.empty()) hel->setAttribute("srcset", resolveSrcset(baseUrl, srcset)); } // Inline style="...: url(...)" (background-image etc.) -- resolved // here rather than where htmlElementToWidgetXml later reads this // same attribute, so that code stays unaware baseUrl even exists. std::string style = hel->getAtributte("style"); if (!style.empty()) hel->setAttribute("style", rewriteCssUrls(style, baseUrl)); if (!style.empty()) { hel->setAttribute("style", rewriteCssUrls(style, [&](const std::string &ref) { return resolveUrl(baseUrl, ref); })); } rewriteRelativeUrls(hel->firstChild(), baseUrl); } Loading Loading @@ -1424,7 +1490,10 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html, // url(...) here is relative to the page itself (this // <style> block is part of the document), unlike an // external stylesheet's own url(...) below. if (!baseUrl.empty()) cssText = rewriteCssUrls(cssText, baseUrl); if (!baseUrl.empty()) { cssText = rewriteCssUrls(cssText, [&](const std::string &ref) { return resolveUrl(baseUrl, ref); }); } libhtmlpp::CSSStyleSheet partial; partial.parse(cssText); for (const auto &rule : partial.getRules()) { Loading Loading @@ -1460,7 +1529,8 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html, // page's -- href is already absolute here // (isAbsolute above, or rewriteRelativeUrls // resolved it against the page baseUrl). cssText = rewriteCssUrls(cssText, href); cssText = rewriteCssUrls(cssText, [&](const std::string &ref) { return resolveUrl(href, ref); }); libhtmlpp::CSSStyleSheet partial; partial.parse(cssText); for (const auto &rule : partial.getRules()) { Loading editor/src/htmlimport.h +20 −0 Changes for editor/src/htmlimport.h: 20 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ #include <vector> #include <set> #include <cstdint> #include <functional> #include <htmlpp/html.h> #include <htmlpp/css.h> Loading Loading @@ -87,6 +88,25 @@ namespace htmlimport { std::string serializeElement(const libhtmlpp::Element *el); std::string serializeInnerHtml(libhtmlpp::HtmlElement *el); /** Finds every url(...) and bare-string @import "..."/'...' (the form * without url()) reference in @p css, in order, and passes each one's * raw reference text (quotes stripped, whitespace trimmed) to @p fn; * @p fn's return value replaces it in the output, with the original * quoting (or lack of it) preserved -- return the input unchanged to * leave that occurrence as-is. Not a CSS tokenizer: it just scans for * these two textual patterns, so e.g. a "url(...)"-shaped substring * inside an unrelated string or comment would also be (mis)matched -- * acceptable given where this is used (best-effort import-time * rewriting, never round-tripping already-published content). * Malformed/unterminated url()/@import is left untouched rather than * risk corrupting the stylesheet. Used both to resolve document- * relative references against a fetched page's URL (see baseUrl on * convertHtmlToWidgetXml) and, by webedit_api.cpp's HTML-import * media-download step, to find/rewrite externally hosted CSS * background images the same way <img> sources are re-hosted. */ std::string rewriteCssUrls(const std::string &css, const std::function<std::string(const std::string&)> &fn); void htmlElementToWidgetXml( libhtmlpp::Element *htmlEl, tinyxml2::XMLDocument &doc, Loading editor/src/webedit_api.cpp +45 −4 Changes for editor/src/webedit_api.cpp: 45 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -5103,8 +5103,17 @@ struct UploadedExternalImage { std::string mediaExt; }; static bool isExternalImageUrl(const std::string &s) { return s.compare(0, 7, "http://") == 0 || s.compare(0, 8, "https://") == 0; } // Recursively collects every distinct absolute http(s) `src` from the // generated widget XML's Image elements, in document order. // generated widget XML's Image elements, plus every absolute http(s) // url(...)/@import reference found in any CustomCss element's text (per- // widget background-image etc., set by extractCustomCss), in document order. // The page-level equivalent (doc.globalCss) isn't reachable from here -- it // lives in DocumentState, not this XML tree -- see its own scan alongside // this call in handleImportHtml. static void collectExternalImageSrcs(tinyxml2::XMLElement *el, std::vector<std::string> &urls, std::set<std::string> &seen) { for (auto *child = el->FirstChildElement(); child; child = child->NextSiblingElement()) { Loading @@ -5112,18 +5121,26 @@ static void collectExternalImageSrcs(tinyxml2::XMLElement *el, std::vector<std:: const char *src = child->Attribute("src"); if (src) { std::string s = src; if ((s.compare(0, 7, "http://") == 0 || s.compare(0, 8, "https://") == 0) && seen.insert(s).second) { if (isExternalImageUrl(s) && seen.insert(s).second) { urls.push_back(s); } } } else if (strcmp(child->Name(), "CustomCss") == 0) { if (const char *text = child->GetText()) { blogi::htmlimport::rewriteCssUrls(text, [&](const std::string &ref) { if (isExternalImageUrl(ref) && seen.insert(ref).second) urls.push_back(ref); return ref; }); } } collectExternalImageSrcs(child, urls, seen); } } // Rewrites every Image element whose (still-external) `src` matches a // successfully uploaded entry to the new mediadb src/media_id/media_ext. // successfully uploaded entry to the new mediadb src/media_id/media_ext, and // every CustomCss element's url(...)/@import reference the same way (see // collectExternalImageSrcs above for where these were found). static void rewriteImageSrcs(tinyxml2::XMLElement *el, const std::unordered_map<std::string, UploadedExternalImage> &uploaded) { for (auto *child = el->FirstChildElement(); child; child = child->NextSiblingElement()) { Loading @@ -5139,6 +5156,15 @@ static void rewriteImageSrcs(tinyxml2::XMLElement *el, child->SetAttribute("media_ext", it->second.mediaExt.c_str()); } } } else if (strcmp(child->Name(), "CustomCss") == 0) { if (const char *text = child->GetText()) { std::string rewritten = blogi::htmlimport::rewriteCssUrls(text, [&](const std::string &ref) { auto it = uploaded.find(ref); return it != uploaded.end() ? it->second.url : ref; }); child->SetText(rewritten.c_str()); } } rewriteImageSrcs(child, uploaded); } Loading Loading @@ -5338,6 +5364,14 @@ void webedit::Api::handleImportHtml(libhttppp::HttpRequest &curreq, std::vector<std::string> externalUrls; std::set<std::string> seenUrls; collectExternalImageSrcs(rootEl, externalUrls, seenUrls); // Page-level CSS (doc.globalCss, already assigned above) lives outside // rootEl's tree, so it needs its own scan for background-image etc. if (!doc.globalCss.empty()) { blogi::htmlimport::rewriteCssUrls(doc.globalCss, [&](const std::string &ref) { if (isExternalImageUrl(ref) && seenUrls.insert(ref).second) externalUrls.push_back(ref); return ref; }); } std::string mediaAuthid, mediaBlogUrl; if (!albumId.empty() && !externalUrls.empty()) { Loading Loading @@ -5427,6 +5461,13 @@ void webedit::Api::handleImportHtml(libhttppp::HttpRequest &curreq, if (!uploaded.empty()) { rewriteImageSrcs(rootEl, uploaded); if (!doc.globalCss.empty()) { doc.globalCss = blogi::htmlimport::rewriteCssUrls(doc.globalCss, [&](const std::string &ref) { auto it = uploaded.find(ref); return it != uploaded.end() ? it->second.url : ref; }); } } } } Loading Loading
editor/src/htmlimport.cpp +110 −40 Changes for editor/src/htmlimport.cpp: 110 added lines, 40 removed lines. Original line number Diff line number Diff line Loading @@ -437,6 +437,78 @@ std::string blogi::htmlimport::serializeInnerHtml(libhtmlpp::HtmlElement *el) { return out.str(); } std::string blogi::htmlimport::rewriteCssUrls( const std::string &css, const std::function<std::string(const std::string&)> &fn) { auto ciMatch = [&](size_t pos, const char *lit) { size_t len = std::strlen(lit); if (pos + len > css.size()) return false; for (size_t k = 0; k < len; ++k) { if (std::tolower(static_cast<unsigned char>(css[pos + k])) != std::tolower(static_cast<unsigned char>(lit[k]))) return false; } return true; }; std::string out; out.reserve(css.size()); size_t i = 0, n = css.size(); while (i < n) { if (ciMatch(i, "url(")) { size_t close = css.find(')', i + 4); if (close == std::string::npos) { out += css[i]; ++i; continue; } std::string inner = css.substr(i + 4, close - (i + 4)); size_t start = inner.find_first_not_of(" \t\r\n"); size_t end = inner.find_last_not_of(" \t\r\n"); std::string ref = (start == std::string::npos) ? "" : inner.substr(start, end - start + 1); char quote = '\0'; if (ref.size() >= 2 && (ref.front() == '"' || ref.front() == '\'') && ref.back() == ref.front()) { quote = ref.front(); ref = ref.substr(1, ref.size() - 2); } out += "url("; if (quote) out += quote; out += fn(ref); if (quote) out += quote; out += ')'; i = close + 1; } else if (ciMatch(i, "@import")) { out += css.substr(i, 7); i += 7; size_t wsStart = i; while (i < n && std::isspace(static_cast<unsigned char>(css[i]))) ++i; out += css.substr(wsStart, i - wsStart); // Bare-string form, e.g. @import "foo.css"; -- the url(...) form // (@import url(foo.css);) is handled by the branch above instead, // on a later pass through this same loop. if (i < n && (css[i] == '"' || css[i] == '\'')) { char q = css[i]; size_t strEnd = css.find(q, i + 1); if (strEnd != std::string::npos) { std::string ref = css.substr(i + 1, strEnd - (i + 1)); out += q; out += fn(ref); out += q; i = strEnd + 1; continue; } } } else { out += css[i]; ++i; } } return out; } void blogi::htmlimport::htmlElementToWidgetXml( libhtmlpp::Element *htmlEl, tinyxml2::XMLDocument &doc, Loading Loading @@ -1050,49 +1122,36 @@ std::string resolveUrl(const std::string &baseUrl, const std::string &ref) return urlOrigin(base) + path; } // Rewrites every url(...) reference found anywhere in @p css (declaration // values -- background/background-image/border-image/list-style-image/mask/ // cursor/@font-face src/... all accept it, so this scans the raw text // instead of enumerating property names) to absolute, using resolveUrl. // Quoted ("...", '...') and unquoted forms are both handled; whatever quoting // (or lack of it) the original used is preserved. Malformed/unterminated // url(...) is left untouched rather than risk corrupting the stylesheet. std::string rewriteCssUrls(const std::string &css, const std::string &baseUrl) // Resolves each URL in an <img>/<source> srcset="..." attribute -- a comma- // separated list of "<url> [descriptor]" candidates (descriptor being a // width like "480w" or pixel density like "2x") -- against baseUrl, leaving // each candidate's descriptor untouched. Doesn't handle a URL that itself // contains a literal comma (the HTML spec's own parsing algorithm for this // attribute is itself just a best-effort heuristic around that same case). std::string resolveSrcset(const std::string &baseUrl, const std::string &srcset) { std::string out; out.reserve(css.size()); size_t i = 0, n = css.size(); while (i < n) { if (i + 4 <= n && std::tolower(static_cast<unsigned char>(css[i])) == 'u' && std::tolower(static_cast<unsigned char>(css[i + 1])) == 'r' && std::tolower(static_cast<unsigned char>(css[i + 2])) == 'l' && css[i + 3] == '(') { size_t close = css.find(')', i + 4); if (close == std::string::npos) { out += css[i]; ++i; continue; } size_t start = 0; while (start <= srcset.size()) { size_t comma = srcset.find(',', start); std::string candidate = srcset.substr(start, comma == std::string::npos ? std::string::npos : comma - start); std::string inner = css.substr(i + 4, close - (i + 4)); size_t start = inner.find_first_not_of(" \t\r\n"); size_t end = inner.find_last_not_of(" \t\r\n"); std::string ref = (start == std::string::npos) ? "" : inner.substr(start, end - start + 1); size_t s = candidate.find_first_not_of(" \t\r\n"); size_t e = candidate.find_last_not_of(" \t\r\n"); if (s != std::string::npos) { candidate = candidate.substr(s, e - s + 1); size_t ws = candidate.find_first_of(" \t\r\n"); std::string url = (ws == std::string::npos) ? candidate : candidate.substr(0, ws); std::string descriptor = (ws == std::string::npos) ? "" : candidate.substr(ws); char quote = '\0'; if (ref.size() >= 2 && (ref.front() == '"' || ref.front() == '\'') && ref.back() == ref.front()) { quote = ref.front(); ref = ref.substr(1, ref.size() - 2); if (!out.empty()) out += ", "; out += resolveUrl(baseUrl, url); out += descriptor; } out += "url("; if (quote) out += quote; out += resolveUrl(baseUrl, ref); if (quote) out += quote; out += ')'; i = close + 1; } else { out += css[i]; ++i; } if (comma == std::string::npos) break; start = comma + 1; } return out; } Loading Loading @@ -1130,12 +1189,19 @@ void rewriteRelativeUrls(libhtmlpp::Element *node, const std::string &baseUrl) std::string href = hel->getAtributte("href"); if (!href.empty()) hel->setAttribute("href", resolveUrl(baseUrl, href)); } if (tag == "img" || tag == "source") { std::string srcset = hel->getAtributte("srcset"); if (!srcset.empty()) hel->setAttribute("srcset", resolveSrcset(baseUrl, srcset)); } // Inline style="...: url(...)" (background-image etc.) -- resolved // here rather than where htmlElementToWidgetXml later reads this // same attribute, so that code stays unaware baseUrl even exists. std::string style = hel->getAtributte("style"); if (!style.empty()) hel->setAttribute("style", rewriteCssUrls(style, baseUrl)); if (!style.empty()) { hel->setAttribute("style", rewriteCssUrls(style, [&](const std::string &ref) { return resolveUrl(baseUrl, ref); })); } rewriteRelativeUrls(hel->firstChild(), baseUrl); } Loading Loading @@ -1424,7 +1490,10 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html, // url(...) here is relative to the page itself (this // <style> block is part of the document), unlike an // external stylesheet's own url(...) below. if (!baseUrl.empty()) cssText = rewriteCssUrls(cssText, baseUrl); if (!baseUrl.empty()) { cssText = rewriteCssUrls(cssText, [&](const std::string &ref) { return resolveUrl(baseUrl, ref); }); } libhtmlpp::CSSStyleSheet partial; partial.parse(cssText); for (const auto &rule : partial.getRules()) { Loading Loading @@ -1460,7 +1529,8 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html, // page's -- href is already absolute here // (isAbsolute above, or rewriteRelativeUrls // resolved it against the page baseUrl). cssText = rewriteCssUrls(cssText, href); cssText = rewriteCssUrls(cssText, [&](const std::string &ref) { return resolveUrl(href, ref); }); libhtmlpp::CSSStyleSheet partial; partial.parse(cssText); for (const auto &rule : partial.getRules()) { Loading
editor/src/htmlimport.h +20 −0 Changes for editor/src/htmlimport.h: 20 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -32,6 +32,7 @@ #include <vector> #include <set> #include <cstdint> #include <functional> #include <htmlpp/html.h> #include <htmlpp/css.h> Loading Loading @@ -87,6 +88,25 @@ namespace htmlimport { std::string serializeElement(const libhtmlpp::Element *el); std::string serializeInnerHtml(libhtmlpp::HtmlElement *el); /** Finds every url(...) and bare-string @import "..."/'...' (the form * without url()) reference in @p css, in order, and passes each one's * raw reference text (quotes stripped, whitespace trimmed) to @p fn; * @p fn's return value replaces it in the output, with the original * quoting (or lack of it) preserved -- return the input unchanged to * leave that occurrence as-is. Not a CSS tokenizer: it just scans for * these two textual patterns, so e.g. a "url(...)"-shaped substring * inside an unrelated string or comment would also be (mis)matched -- * acceptable given where this is used (best-effort import-time * rewriting, never round-tripping already-published content). * Malformed/unterminated url()/@import is left untouched rather than * risk corrupting the stylesheet. Used both to resolve document- * relative references against a fetched page's URL (see baseUrl on * convertHtmlToWidgetXml) and, by webedit_api.cpp's HTML-import * media-download step, to find/rewrite externally hosted CSS * background images the same way <img> sources are re-hosted. */ std::string rewriteCssUrls(const std::string &css, const std::function<std::string(const std::string&)> &fn); void htmlElementToWidgetXml( libhtmlpp::Element *htmlEl, tinyxml2::XMLDocument &doc, Loading
editor/src/webedit_api.cpp +45 −4 Changes for editor/src/webedit_api.cpp: 45 added lines, 4 removed lines. Original line number Diff line number Diff line Loading @@ -5103,8 +5103,17 @@ struct UploadedExternalImage { std::string mediaExt; }; static bool isExternalImageUrl(const std::string &s) { return s.compare(0, 7, "http://") == 0 || s.compare(0, 8, "https://") == 0; } // Recursively collects every distinct absolute http(s) `src` from the // generated widget XML's Image elements, in document order. // generated widget XML's Image elements, plus every absolute http(s) // url(...)/@import reference found in any CustomCss element's text (per- // widget background-image etc., set by extractCustomCss), in document order. // The page-level equivalent (doc.globalCss) isn't reachable from here -- it // lives in DocumentState, not this XML tree -- see its own scan alongside // this call in handleImportHtml. static void collectExternalImageSrcs(tinyxml2::XMLElement *el, std::vector<std::string> &urls, std::set<std::string> &seen) { for (auto *child = el->FirstChildElement(); child; child = child->NextSiblingElement()) { Loading @@ -5112,18 +5121,26 @@ static void collectExternalImageSrcs(tinyxml2::XMLElement *el, std::vector<std:: const char *src = child->Attribute("src"); if (src) { std::string s = src; if ((s.compare(0, 7, "http://") == 0 || s.compare(0, 8, "https://") == 0) && seen.insert(s).second) { if (isExternalImageUrl(s) && seen.insert(s).second) { urls.push_back(s); } } } else if (strcmp(child->Name(), "CustomCss") == 0) { if (const char *text = child->GetText()) { blogi::htmlimport::rewriteCssUrls(text, [&](const std::string &ref) { if (isExternalImageUrl(ref) && seen.insert(ref).second) urls.push_back(ref); return ref; }); } } collectExternalImageSrcs(child, urls, seen); } } // Rewrites every Image element whose (still-external) `src` matches a // successfully uploaded entry to the new mediadb src/media_id/media_ext. // successfully uploaded entry to the new mediadb src/media_id/media_ext, and // every CustomCss element's url(...)/@import reference the same way (see // collectExternalImageSrcs above for where these were found). static void rewriteImageSrcs(tinyxml2::XMLElement *el, const std::unordered_map<std::string, UploadedExternalImage> &uploaded) { for (auto *child = el->FirstChildElement(); child; child = child->NextSiblingElement()) { Loading @@ -5139,6 +5156,15 @@ static void rewriteImageSrcs(tinyxml2::XMLElement *el, child->SetAttribute("media_ext", it->second.mediaExt.c_str()); } } } else if (strcmp(child->Name(), "CustomCss") == 0) { if (const char *text = child->GetText()) { std::string rewritten = blogi::htmlimport::rewriteCssUrls(text, [&](const std::string &ref) { auto it = uploaded.find(ref); return it != uploaded.end() ? it->second.url : ref; }); child->SetText(rewritten.c_str()); } } rewriteImageSrcs(child, uploaded); } Loading Loading @@ -5338,6 +5364,14 @@ void webedit::Api::handleImportHtml(libhttppp::HttpRequest &curreq, std::vector<std::string> externalUrls; std::set<std::string> seenUrls; collectExternalImageSrcs(rootEl, externalUrls, seenUrls); // Page-level CSS (doc.globalCss, already assigned above) lives outside // rootEl's tree, so it needs its own scan for background-image etc. if (!doc.globalCss.empty()) { blogi::htmlimport::rewriteCssUrls(doc.globalCss, [&](const std::string &ref) { if (isExternalImageUrl(ref) && seenUrls.insert(ref).second) externalUrls.push_back(ref); return ref; }); } std::string mediaAuthid, mediaBlogUrl; if (!albumId.empty() && !externalUrls.empty()) { Loading Loading @@ -5427,6 +5461,13 @@ void webedit::Api::handleImportHtml(libhttppp::HttpRequest &curreq, if (!uploaded.empty()) { rewriteImageSrcs(rootEl, uploaded); if (!doc.globalCss.empty()) { doc.globalCss = blogi::htmlimport::rewriteCssUrls(doc.globalCss, [&](const std::string &ref) { auto it = uploaded.find(ref); return it != uploaded.end() ? it->second.url : ref; }); } } } } Loading