Loading editor/src/htmlimport.cpp +40 −5 Changes for editor/src/htmlimport.cpp: 40 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -260,7 +260,8 @@ void blogi::htmlimport::mergeStylesheetRules( const std::string &cssClass, const std::string &id, std::map<std::string,std::string> &props, std::string &mediaRules) std::string &mediaRules, std::set<std::string> &seenMediaBlocks) { std::vector<std::string> classes; if (!cssClass.empty()) { Loading Loading @@ -313,19 +314,38 @@ void blogi::htmlimport::mergeStylesheetRules( singleSel = singleSel.substr(start, end - start + 1); std::string matchSel = singleSel; bool hadCombinator = false; { size_t lastSep = matchSel.find_last_of(" >+~"); if (lastSep != std::string::npos) { size_t mstart = matchSel.find_first_not_of(" >+~", lastSep); if (mstart != std::string::npos) { matchSel = matchSel.substr(mstart); hadCombinator = true; } } } bool matches = false; if (!matchSel.empty() && matchSel[0] == '#') { // A bare tag name left over after stripping a combinator's // ancestor part (e.g. "[data-atom=header]>div" reduces to "div") // can't be verified here -- this function only ever sees the // current element's own tag/class/id, never its ancestor chain // -- so treating it as a match would apply the rule to literally // every element of that tag in the whole document. Confirmed // against a real scraped page where exactly this pattern matched // ~24,000 times and blew up the imported output to 36MB. A // class/id qualifier on the rightmost compound (e.g. "div.foo") // is still specific enough to check below. bool bareTagAfterCombinator = hadCombinator && !matchSel.empty() && matchSel[0] != '#' && matchSel[0] != '.' && matchSel.find('.') == std::string::npos && matchSel.find('#') == std::string::npos; if (bareTagAfterCombinator) { // matches stays false } else if (!matchSel.empty() && matchSel[0] == '#') { if (!id.empty() && matchSel == "#" + id) matches = true; } else if (!matchSel.empty() && matchSel[0] == '.') { std::string clsSel = matchSel.substr(1); Loading Loading @@ -368,7 +388,16 @@ void blogi::htmlimport::mergeStylesheetRules( block += prop.getName() + ": " + prop.getValue() + "; "; } block += "} } "; // The same @media block, once present anywhere in the // final page's HTML, applies document-wide regardless of // which widget's <style> tag carries it -- so including // it more than once across the whole import is pure // bloat, not a correctness requirement. Real scraped // pages routinely have many widgets share one rule (e.g. // by class), each independently re-matching it here. if (seenMediaBlocks.insert(block).second) { mediaRules += block; } } else { for (const auto &prop : rule.getDeclaration().getProperties()) { if (props.find(prop.getName()) == props.end()) { Loading Loading @@ -573,6 +602,7 @@ void blogi::htmlimport::htmlElementToWidgetXml( tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parentXml, const libhtmlpp::CSSStyleSheet &sheet, std::set<std::string> &seenMediaBlocks, libhtmlpp::Element *stopBefore) { if (!htmlEl) return; Loading Loading @@ -650,7 +680,7 @@ void blogi::htmlimport::htmlElementToWidgetXml( std::string elemId = el->getAtributte("id"); std::string mediaRules; mergeStylesheetRules(sheet, tag, cssClass, elemId, cssProps, mediaRules); mergeStylesheetRules(sheet, tag, cssClass, elemId, cssProps, mediaRules, seenMediaBlocks); // ---- Map HTML tags to widget types ---- Loading Loading @@ -1824,16 +1854,21 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html, xmlRoot->SetAttribute("GlobalJs", globalJs.c_str()); } // Shared across every top-level node imported below (not per-node) so // an @media block already emitted via one node's widgets isn't repeated // by another's -- see mergeStylesheetRules's dedup comment. std::set<std::string> seenMediaBlocks; if (root) { if (selectedPaths) { for (const auto &path : *selectedPaths) { libhtmlpp::Element *el = resolveImportPath(root, path); if (el) { htmlElementToWidgetXml(el, xmlDoc, xmlRoot, stylesheet, el->nextElement()); htmlElementToWidgetXml(el, xmlDoc, xmlRoot, stylesheet, seenMediaBlocks, el->nextElement()); } } } else { htmlElementToWidgetXml(root, xmlDoc, xmlRoot, stylesheet); htmlElementToWidgetXml(root, xmlDoc, xmlRoot, stylesheet, seenMediaBlocks); } } 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 @@ -74,13 +74,24 @@ namespace htmlimport { std::map<std::string,std::string> declarationToMap( const libhtmlpp::CSSDeclaration &decl); /** @p seenMediaBlocks accumulates the exact text of every @media block * already appended to some element's @p mediaRules across the whole * import (see convertHtmlToWidgetXml/htmlElementToWidgetXml, which * share one instance across every element processed) -- a block whose * text is already present is skipped rather than appended again. This * is safe because a <style> block's effect is document-wide regardless * of which widget's XML happens to carry it, and it matters because a * rule matched by a real page can independently match thousands of * elements (e.g. by shared class), each otherwise re-appending the * identical block text. */ void mergeStylesheetRules( const libhtmlpp::CSSStyleSheet &sheet, const std::string &tag, const std::string &cssClass, const std::string &id, std::map<std::string,std::string> &props, std::string &mediaRules); std::string &mediaRules, std::set<std::string> &seenMediaBlocks); std::string extractCustomCss( std::map<std::string,std::string> &props, Loading Loading @@ -116,6 +127,7 @@ namespace htmlimport { tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parentXml, const libhtmlpp::CSSStyleSheet &sheet, std::set<std::string> &seenMediaBlocks, libhtmlpp::Element *stopBefore = nullptr); /** One node of the HTML-import block picker's tree: an element reachable Loading Loading
editor/src/htmlimport.cpp +40 −5 Changes for editor/src/htmlimport.cpp: 40 added lines, 5 removed lines. Original line number Diff line number Diff line Loading @@ -260,7 +260,8 @@ void blogi::htmlimport::mergeStylesheetRules( const std::string &cssClass, const std::string &id, std::map<std::string,std::string> &props, std::string &mediaRules) std::string &mediaRules, std::set<std::string> &seenMediaBlocks) { std::vector<std::string> classes; if (!cssClass.empty()) { Loading Loading @@ -313,19 +314,38 @@ void blogi::htmlimport::mergeStylesheetRules( singleSel = singleSel.substr(start, end - start + 1); std::string matchSel = singleSel; bool hadCombinator = false; { size_t lastSep = matchSel.find_last_of(" >+~"); if (lastSep != std::string::npos) { size_t mstart = matchSel.find_first_not_of(" >+~", lastSep); if (mstart != std::string::npos) { matchSel = matchSel.substr(mstart); hadCombinator = true; } } } bool matches = false; if (!matchSel.empty() && matchSel[0] == '#') { // A bare tag name left over after stripping a combinator's // ancestor part (e.g. "[data-atom=header]>div" reduces to "div") // can't be verified here -- this function only ever sees the // current element's own tag/class/id, never its ancestor chain // -- so treating it as a match would apply the rule to literally // every element of that tag in the whole document. Confirmed // against a real scraped page where exactly this pattern matched // ~24,000 times and blew up the imported output to 36MB. A // class/id qualifier on the rightmost compound (e.g. "div.foo") // is still specific enough to check below. bool bareTagAfterCombinator = hadCombinator && !matchSel.empty() && matchSel[0] != '#' && matchSel[0] != '.' && matchSel.find('.') == std::string::npos && matchSel.find('#') == std::string::npos; if (bareTagAfterCombinator) { // matches stays false } else if (!matchSel.empty() && matchSel[0] == '#') { if (!id.empty() && matchSel == "#" + id) matches = true; } else if (!matchSel.empty() && matchSel[0] == '.') { std::string clsSel = matchSel.substr(1); Loading Loading @@ -368,7 +388,16 @@ void blogi::htmlimport::mergeStylesheetRules( block += prop.getName() + ": " + prop.getValue() + "; "; } block += "} } "; // The same @media block, once present anywhere in the // final page's HTML, applies document-wide regardless of // which widget's <style> tag carries it -- so including // it more than once across the whole import is pure // bloat, not a correctness requirement. Real scraped // pages routinely have many widgets share one rule (e.g. // by class), each independently re-matching it here. if (seenMediaBlocks.insert(block).second) { mediaRules += block; } } else { for (const auto &prop : rule.getDeclaration().getProperties()) { if (props.find(prop.getName()) == props.end()) { Loading Loading @@ -573,6 +602,7 @@ void blogi::htmlimport::htmlElementToWidgetXml( tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parentXml, const libhtmlpp::CSSStyleSheet &sheet, std::set<std::string> &seenMediaBlocks, libhtmlpp::Element *stopBefore) { if (!htmlEl) return; Loading Loading @@ -650,7 +680,7 @@ void blogi::htmlimport::htmlElementToWidgetXml( std::string elemId = el->getAtributte("id"); std::string mediaRules; mergeStylesheetRules(sheet, tag, cssClass, elemId, cssProps, mediaRules); mergeStylesheetRules(sheet, tag, cssClass, elemId, cssProps, mediaRules, seenMediaBlocks); // ---- Map HTML tags to widget types ---- Loading Loading @@ -1824,16 +1854,21 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html, xmlRoot->SetAttribute("GlobalJs", globalJs.c_str()); } // Shared across every top-level node imported below (not per-node) so // an @media block already emitted via one node's widgets isn't repeated // by another's -- see mergeStylesheetRules's dedup comment. std::set<std::string> seenMediaBlocks; if (root) { if (selectedPaths) { for (const auto &path : *selectedPaths) { libhtmlpp::Element *el = resolveImportPath(root, path); if (el) { htmlElementToWidgetXml(el, xmlDoc, xmlRoot, stylesheet, el->nextElement()); htmlElementToWidgetXml(el, xmlDoc, xmlRoot, stylesheet, seenMediaBlocks, el->nextElement()); } } } else { htmlElementToWidgetXml(root, xmlDoc, xmlRoot, stylesheet); htmlElementToWidgetXml(root, xmlDoc, xmlRoot, stylesheet, seenMediaBlocks); } } 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 @@ -74,13 +74,24 @@ namespace htmlimport { std::map<std::string,std::string> declarationToMap( const libhtmlpp::CSSDeclaration &decl); /** @p seenMediaBlocks accumulates the exact text of every @media block * already appended to some element's @p mediaRules across the whole * import (see convertHtmlToWidgetXml/htmlElementToWidgetXml, which * share one instance across every element processed) -- a block whose * text is already present is skipped rather than appended again. This * is safe because a <style> block's effect is document-wide regardless * of which widget's XML happens to carry it, and it matters because a * rule matched by a real page can independently match thousands of * elements (e.g. by shared class), each otherwise re-appending the * identical block text. */ void mergeStylesheetRules( const libhtmlpp::CSSStyleSheet &sheet, const std::string &tag, const std::string &cssClass, const std::string &id, std::map<std::string,std::string> &props, std::string &mediaRules); std::string &mediaRules, std::set<std::string> &seenMediaBlocks); std::string extractCustomCss( std::map<std::string,std::string> &props, Loading Loading @@ -116,6 +127,7 @@ namespace htmlimport { tinyxml2::XMLDocument &doc, tinyxml2::XMLElement *parentXml, const libhtmlpp::CSSStyleSheet &sheet, std::set<std::string> &seenMediaBlocks, libhtmlpp::Element *stopBefore = nullptr); /** One node of the HTML-import block picker's tree: an element reachable Loading