Loading editor/edit.cpp +8 −139 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ #include "edit.h" #include "plugin.h" #include "htmlimport.h" #include "blogi.h" #include "theme.h" Loading Loading @@ -1261,148 +1262,16 @@ void blogi::webedit::WebEditor::importHtmlToWidgets(const std::string &html) { CurDoc.Clear(); CurRootEl = nullptr; // Parse the HTML using libhtmlpp (proper HTML parser) libhtmlpp::HtmlString htmlStr(html); libhtmlpp::Element *root = nullptr; try { root = &htmlStr.parse(); } catch (...) { // Fallback: import as single CustomHtml widget CurDoc.Clear(); CurRootEl = CurDoc.NewElement("WebEditor"); CurRootEl->SetAttribute("Version", "1.0"); CurDoc.InsertEndChild(CurRootEl); tinyxml2::XMLElement *w = CurDoc.NewElement("CustomHtml"); tinyxml2::XMLElement *htmlContent = CurDoc.NewElement("HtmlContent"); htmlContent->SetText(html.c_str()); w->InsertEndChild(htmlContent); CurRootEl->InsertEndChild(w); loadWidgetsFromXml(CurRootEl, nullptr); return; } // Collect all <style> elements from the DOM and parse CSS libhtmlpp::CSSStyleSheet stylesheet; std::stack<libhtmlpp::Element*> searchStack; searchStack.push(root); while (!searchStack.empty()) { libhtmlpp::Element *cur = searchStack.top(); searchStack.pop(); while (cur) { if (cur->getType() == libhtmlpp::HtmlEl) { libhtmlpp::HtmlElement *hel = static_cast<libhtmlpp::HtmlElement*>(cur); std::string tagName = hel->getTagname(); std::transform(tagName.begin(), tagName.end(), tagName.begin(), [](unsigned char c) { return std::tolower(c); }); if (tagName == "style") { // Get text content of style element std::string cssText; libhtmlpp::Element *styleChild = hel->firstChild(); while (styleChild) { if (styleChild->getType() == libhtmlpp::TextEl) { cssText += static_cast<libhtmlpp::TextElement*>(styleChild)->getText(); } styleChild = styleChild->nextElement(); } if (!cssText.empty()) { libhtmlpp::CSSStyleSheet partial; partial.parse(cssText); for (const auto &rule : partial.getRules()) { stylesheet.addRule(rule); } } } // Also search children of non-style elements if (tagName != "style") { libhtmlpp::Element *child = hel->firstChild(); if (child) searchStack.push(child); } } cur = cur->nextElement(); } } // Build widget XML CurDoc.Clear(); CurRootEl = CurDoc.NewElement("WebEditor"); CurRootEl->SetAttribute("Version", "1.0"); CurDoc.InsertEndChild(CurRootEl); // Emit global CSS rules that can't be matched to individual elements: // :root (CSS variables), @keyframes, @font-face, *, html, body, // ::selection, ::-webkit-scrollbar, and other pseudo-elements. std::string outXml; std::string globalCss; for (const auto &rule : stylesheet.getRules()) { const std::string &sel = rule.getSelector(); if (sel.empty()) continue; bool isGlobal = false; if (sel[0] == '@') { // @media with inner selectors are handled per-element in mergeStylesheetRules. // Everything else (@keyframes, @font-face, @import, @charset, bare @media // with :root/::-scrollbar, etc.) is global. // Check if it has parentheses with an inner selector — those go per-element. size_t parenClose = std::string::npos; size_t parenDepth = 0; for (size_t i = 0; i < sel.size(); ++i) { if (sel[i] == '(') ++parenDepth; else if (sel[i] == ')') { if (parenDepth > 0) --parenDepth; if (parenDepth == 0) { parenClose = i; break; } } } if (parenClose != std::string::npos && parenClose + 1 < sel.size()) { // Has inner selector (e.g. "@media (...) .class") → per-element, skip // unless the inner selector is :root or other global pseudo size_t innerStart = sel.find_first_not_of(" \t\n\r", parenClose + 1); if (innerStart != std::string::npos) { std::string inner = sel.substr(innerStart); std::string innerLower = inner; std::transform(innerLower.begin(), innerLower.end(), innerLower.begin(), [](unsigned char c) { return std::tolower(c); }); if (innerLower.compare(0, 5, ":root") == 0 || innerLower.find("::") != std::string::npos || innerLower == "*" || innerLower == "html" || innerLower == "body") { isGlobal = true; } } } else { // No inner selector: @keyframes, @font-face, @import, @charset, etc. isGlobal = true; } } else { std::string selLower = sel; std::transform(selLower.begin(), selLower.end(), selLower.begin(), [](unsigned char c) { return std::tolower(c); }); if (selLower == ":root" || selLower == "*" || selLower == "html" || selLower == "body" || selLower.compare(0, 5, ":root") == 0 || selLower.find("::") != std::string::npos) { isGlobal = true; } } if (isGlobal) { globalCss += rule.serialize(true); globalCss += "\n"; } } if (!globalCss.empty()) { if (blogi::htmlimport::convertHtmlToWidgetXml(html, outXml, globalCss)) { GlobalCss = globalCss; CurRootEl->SetAttribute("GlobalCss", GlobalCss.c_str()); } else { GlobalCss.clear(); CurDoc.Parse(outXml.c_str()); CurRootEl = CurDoc.RootElement(); if (CurRootEl) { loadWidgetsFromXml(CurRootEl, nullptr); } if (root) { htmlElementToWidgetXml(root, CurDoc, CurRootEl, stylesheet); } // Load the generated widget XML into the live tree loadWidgetsFromXml(CurRootEl, nullptr); } void blogi::webedit::WebEditor::saveDocumentXml(libhttppp::HttpRequest &curreq) { Loading editor/htmlimport.cpp +4 −2 Original line number Diff line number Diff line Loading @@ -720,7 +720,8 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html, std::string &outXml, std::string &outGlobalCss) { libhtmlpp::HtmlString htmlStr(html); libhtmlpp::HtmlPage page; libhtmlpp::HtmlElement index; libhtmlpp::Element *root = nullptr; tinyxml2::XMLDocument xmlDoc; Loading @@ -730,7 +731,8 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html, xmlDoc.InsertEndChild(xmlRoot); try { root = &htmlStr.parse(); page.loadString(index, html); root = &index; } catch (...) { // Fallback: single CustomHtml widget with raw HTML tinyxml2::XMLElement *w = xmlDoc.NewElement("CustomHtml"); Loading Loading
editor/edit.cpp +8 −139 Original line number Diff line number Diff line Loading @@ -41,6 +41,7 @@ #include "edit.h" #include "plugin.h" #include "htmlimport.h" #include "blogi.h" #include "theme.h" Loading Loading @@ -1261,148 +1262,16 @@ void blogi::webedit::WebEditor::importHtmlToWidgets(const std::string &html) { CurDoc.Clear(); CurRootEl = nullptr; // Parse the HTML using libhtmlpp (proper HTML parser) libhtmlpp::HtmlString htmlStr(html); libhtmlpp::Element *root = nullptr; try { root = &htmlStr.parse(); } catch (...) { // Fallback: import as single CustomHtml widget CurDoc.Clear(); CurRootEl = CurDoc.NewElement("WebEditor"); CurRootEl->SetAttribute("Version", "1.0"); CurDoc.InsertEndChild(CurRootEl); tinyxml2::XMLElement *w = CurDoc.NewElement("CustomHtml"); tinyxml2::XMLElement *htmlContent = CurDoc.NewElement("HtmlContent"); htmlContent->SetText(html.c_str()); w->InsertEndChild(htmlContent); CurRootEl->InsertEndChild(w); loadWidgetsFromXml(CurRootEl, nullptr); return; } // Collect all <style> elements from the DOM and parse CSS libhtmlpp::CSSStyleSheet stylesheet; std::stack<libhtmlpp::Element*> searchStack; searchStack.push(root); while (!searchStack.empty()) { libhtmlpp::Element *cur = searchStack.top(); searchStack.pop(); while (cur) { if (cur->getType() == libhtmlpp::HtmlEl) { libhtmlpp::HtmlElement *hel = static_cast<libhtmlpp::HtmlElement*>(cur); std::string tagName = hel->getTagname(); std::transform(tagName.begin(), tagName.end(), tagName.begin(), [](unsigned char c) { return std::tolower(c); }); if (tagName == "style") { // Get text content of style element std::string cssText; libhtmlpp::Element *styleChild = hel->firstChild(); while (styleChild) { if (styleChild->getType() == libhtmlpp::TextEl) { cssText += static_cast<libhtmlpp::TextElement*>(styleChild)->getText(); } styleChild = styleChild->nextElement(); } if (!cssText.empty()) { libhtmlpp::CSSStyleSheet partial; partial.parse(cssText); for (const auto &rule : partial.getRules()) { stylesheet.addRule(rule); } } } // Also search children of non-style elements if (tagName != "style") { libhtmlpp::Element *child = hel->firstChild(); if (child) searchStack.push(child); } } cur = cur->nextElement(); } } // Build widget XML CurDoc.Clear(); CurRootEl = CurDoc.NewElement("WebEditor"); CurRootEl->SetAttribute("Version", "1.0"); CurDoc.InsertEndChild(CurRootEl); // Emit global CSS rules that can't be matched to individual elements: // :root (CSS variables), @keyframes, @font-face, *, html, body, // ::selection, ::-webkit-scrollbar, and other pseudo-elements. std::string outXml; std::string globalCss; for (const auto &rule : stylesheet.getRules()) { const std::string &sel = rule.getSelector(); if (sel.empty()) continue; bool isGlobal = false; if (sel[0] == '@') { // @media with inner selectors are handled per-element in mergeStylesheetRules. // Everything else (@keyframes, @font-face, @import, @charset, bare @media // with :root/::-scrollbar, etc.) is global. // Check if it has parentheses with an inner selector — those go per-element. size_t parenClose = std::string::npos; size_t parenDepth = 0; for (size_t i = 0; i < sel.size(); ++i) { if (sel[i] == '(') ++parenDepth; else if (sel[i] == ')') { if (parenDepth > 0) --parenDepth; if (parenDepth == 0) { parenClose = i; break; } } } if (parenClose != std::string::npos && parenClose + 1 < sel.size()) { // Has inner selector (e.g. "@media (...) .class") → per-element, skip // unless the inner selector is :root or other global pseudo size_t innerStart = sel.find_first_not_of(" \t\n\r", parenClose + 1); if (innerStart != std::string::npos) { std::string inner = sel.substr(innerStart); std::string innerLower = inner; std::transform(innerLower.begin(), innerLower.end(), innerLower.begin(), [](unsigned char c) { return std::tolower(c); }); if (innerLower.compare(0, 5, ":root") == 0 || innerLower.find("::") != std::string::npos || innerLower == "*" || innerLower == "html" || innerLower == "body") { isGlobal = true; } } } else { // No inner selector: @keyframes, @font-face, @import, @charset, etc. isGlobal = true; } } else { std::string selLower = sel; std::transform(selLower.begin(), selLower.end(), selLower.begin(), [](unsigned char c) { return std::tolower(c); }); if (selLower == ":root" || selLower == "*" || selLower == "html" || selLower == "body" || selLower.compare(0, 5, ":root") == 0 || selLower.find("::") != std::string::npos) { isGlobal = true; } } if (isGlobal) { globalCss += rule.serialize(true); globalCss += "\n"; } } if (!globalCss.empty()) { if (blogi::htmlimport::convertHtmlToWidgetXml(html, outXml, globalCss)) { GlobalCss = globalCss; CurRootEl->SetAttribute("GlobalCss", GlobalCss.c_str()); } else { GlobalCss.clear(); CurDoc.Parse(outXml.c_str()); CurRootEl = CurDoc.RootElement(); if (CurRootEl) { loadWidgetsFromXml(CurRootEl, nullptr); } if (root) { htmlElementToWidgetXml(root, CurDoc, CurRootEl, stylesheet); } // Load the generated widget XML into the live tree loadWidgetsFromXml(CurRootEl, nullptr); } void blogi::webedit::WebEditor::saveDocumentXml(libhttppp::HttpRequest &curreq) { Loading
editor/htmlimport.cpp +4 −2 Original line number Diff line number Diff line Loading @@ -720,7 +720,8 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html, std::string &outXml, std::string &outGlobalCss) { libhtmlpp::HtmlString htmlStr(html); libhtmlpp::HtmlPage page; libhtmlpp::HtmlElement index; libhtmlpp::Element *root = nullptr; tinyxml2::XMLDocument xmlDoc; Loading @@ -730,7 +731,8 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html, xmlDoc.InsertEndChild(xmlRoot); try { root = &htmlStr.parse(); page.loadString(index, html); root = &index; } catch (...) { // Fallback: single CustomHtml widget with raw HTML tinyxml2::XMLElement *w = xmlDoc.NewElement("CustomHtml"); Loading