Loading src/htmlcss.cpp +4 −2 Original line number Diff line number Diff line Loading @@ -115,7 +115,8 @@ void libhtmlpp::collectStyleBlocks(CSSStyleSheet &sheet, Element &root) libhtmlpp::CSSRuleResult libhtmlpp::getCSSRules(HtmlElement &target, const CSSStyleSheet &sheet, std::set<std::string> &seenMediaBlocks) std::set<std::string> &seenMediaBlocks, const std::vector<AncestorFrame> *ancestors) { CSSRuleResult result; Loading @@ -130,6 +131,7 @@ libhtmlpp::CSSRuleResult libhtmlpp::getCSSRules(HtmlElement &target, std::string cssClass = target.getAtributte("class"); std::string id = target.getAtributte("id"); sheet.collectApproximateMatches(tag, cssClass, id, result.properties, result.mediaRules, seenMediaBlocks); sheet.collectApproximateMatches(tag, cssClass, id, result.properties, result.mediaRules, seenMediaBlocks, ancestors); return result; } src/htmlcss.h +7 −1 Original line number Diff line number Diff line Loading @@ -75,7 +75,13 @@ namespace libhtmlpp { * across many calls (e.g. one per element while walking a whole * document) to avoid accumulating the exact same @media block's text * once per matching element -- see * CSSStyleSheet::collectApproximateMatches. */ * CSSStyleSheet::collectApproximateMatches. @p ancestors is forwarded to * CSSStyleSheet::collectApproximateMatches exactly as described there: * nullptr (the default) means no ancestor/combinator verification * (this function's original behavior), a non-null chain (see * AncestorFrame) makes a descendant selector's leading compounds * required to actually be found among @p target's ancestors, in order, * instead of being ignored. */ CSSRuleResult getCSSRules(HtmlElement &target, const CSSStyleSheet &sheet, std::set<std::string> &seenMediaBlocks, Loading test/htmlcsstest.cpp +49 −0 Original line number Diff line number Diff line Loading @@ -259,6 +259,55 @@ int main(){ } } // --- getCSSRules with an ancestor chain: the real bug, end-to-end --- std::cout << "=== getCSSRules with ancestor chain ===" << std::endl; { using libhtmlpp::AncestorFrame; std::string html = "<html><head>" "<style>" ".frame { padding-top: 100px; }" ".header .frame { padding-top: 0; }" "</style>" "</head><body>" "<div class=\"header\"><div id=\"headerFrame\" class=\"frame\">hi</div></div>" "<div class=\"hero\"><div id=\"heroFrame\" class=\"frame\">hi</div></div>" "</body></html>"; libhtmlpp::HtmlString htmlString(html); libhtmlpp::Element &root = htmlString.parse(); libhtmlpp::CSSStyleSheet sheet; libhtmlpp::collectStyleBlocks(sheet, root); libhtmlpp::HtmlElement *headerFrame = static_cast<libhtmlpp::HtmlElement*>(&root)->getElementbyID("headerFrame"); libhtmlpp::HtmlElement *heroFrame = static_cast<libhtmlpp::HtmlElement*>(&root)->getElementbyID("heroFrame"); check(headerFrame != nullptr && heroFrame != nullptr, "found both target elements by id"); if (headerFrame && heroFrame) { std::set<std::string> seen; auto noAncestorsResult = libhtmlpp::getCSSRules(*heroFrame, sheet, seen); check(noAncestorsResult.properties["padding-top"] == "0", "without an ancestor chain, the header-only rule still bleeds onto an unrelated " "element -- this is the pre-fix behavior, unchanged for callers that pass none"); std::vector<AncestorFrame> headerAncestors = {{"div", {"header"}, ""}}; std::set<std::string> seen2; auto headerResult = libhtmlpp::getCSSRules(*headerFrame, sheet, seen2, &headerAncestors); check(headerResult.properties["padding-top"] == "0", "with its real ancestor chain, the header's own frame still gets the header-only rule"); std::vector<AncestorFrame> heroAncestors = {{"div", {"hero"}, ""}}; std::set<std::string> seen3; auto heroResult = libhtmlpp::getCSSRules(*heroFrame, sheet, seen3, &heroAncestors); check(heroResult.properties["padding-top"] == "100px", "with its real (non-header) ancestor chain, the hero's frame keeps the general " "rule instead of the header-only one -- the actual bug fix"); } } // --- resolveCSSVariables --- std::cout << "=== resolveCSSVariables ===" << std::endl; { Loading Loading
src/htmlcss.cpp +4 −2 Original line number Diff line number Diff line Loading @@ -115,7 +115,8 @@ void libhtmlpp::collectStyleBlocks(CSSStyleSheet &sheet, Element &root) libhtmlpp::CSSRuleResult libhtmlpp::getCSSRules(HtmlElement &target, const CSSStyleSheet &sheet, std::set<std::string> &seenMediaBlocks) std::set<std::string> &seenMediaBlocks, const std::vector<AncestorFrame> *ancestors) { CSSRuleResult result; Loading @@ -130,6 +131,7 @@ libhtmlpp::CSSRuleResult libhtmlpp::getCSSRules(HtmlElement &target, std::string cssClass = target.getAtributte("class"); std::string id = target.getAtributte("id"); sheet.collectApproximateMatches(tag, cssClass, id, result.properties, result.mediaRules, seenMediaBlocks); sheet.collectApproximateMatches(tag, cssClass, id, result.properties, result.mediaRules, seenMediaBlocks, ancestors); return result; }
src/htmlcss.h +7 −1 Original line number Diff line number Diff line Loading @@ -75,7 +75,13 @@ namespace libhtmlpp { * across many calls (e.g. one per element while walking a whole * document) to avoid accumulating the exact same @media block's text * once per matching element -- see * CSSStyleSheet::collectApproximateMatches. */ * CSSStyleSheet::collectApproximateMatches. @p ancestors is forwarded to * CSSStyleSheet::collectApproximateMatches exactly as described there: * nullptr (the default) means no ancestor/combinator verification * (this function's original behavior), a non-null chain (see * AncestorFrame) makes a descendant selector's leading compounds * required to actually be found among @p target's ancestors, in order, * instead of being ignored. */ CSSRuleResult getCSSRules(HtmlElement &target, const CSSStyleSheet &sheet, std::set<std::string> &seenMediaBlocks, Loading
test/htmlcsstest.cpp +49 −0 Original line number Diff line number Diff line Loading @@ -259,6 +259,55 @@ int main(){ } } // --- getCSSRules with an ancestor chain: the real bug, end-to-end --- std::cout << "=== getCSSRules with ancestor chain ===" << std::endl; { using libhtmlpp::AncestorFrame; std::string html = "<html><head>" "<style>" ".frame { padding-top: 100px; }" ".header .frame { padding-top: 0; }" "</style>" "</head><body>" "<div class=\"header\"><div id=\"headerFrame\" class=\"frame\">hi</div></div>" "<div class=\"hero\"><div id=\"heroFrame\" class=\"frame\">hi</div></div>" "</body></html>"; libhtmlpp::HtmlString htmlString(html); libhtmlpp::Element &root = htmlString.parse(); libhtmlpp::CSSStyleSheet sheet; libhtmlpp::collectStyleBlocks(sheet, root); libhtmlpp::HtmlElement *headerFrame = static_cast<libhtmlpp::HtmlElement*>(&root)->getElementbyID("headerFrame"); libhtmlpp::HtmlElement *heroFrame = static_cast<libhtmlpp::HtmlElement*>(&root)->getElementbyID("heroFrame"); check(headerFrame != nullptr && heroFrame != nullptr, "found both target elements by id"); if (headerFrame && heroFrame) { std::set<std::string> seen; auto noAncestorsResult = libhtmlpp::getCSSRules(*heroFrame, sheet, seen); check(noAncestorsResult.properties["padding-top"] == "0", "without an ancestor chain, the header-only rule still bleeds onto an unrelated " "element -- this is the pre-fix behavior, unchanged for callers that pass none"); std::vector<AncestorFrame> headerAncestors = {{"div", {"header"}, ""}}; std::set<std::string> seen2; auto headerResult = libhtmlpp::getCSSRules(*headerFrame, sheet, seen2, &headerAncestors); check(headerResult.properties["padding-top"] == "0", "with its real ancestor chain, the header's own frame still gets the header-only rule"); std::vector<AncestorFrame> heroAncestors = {{"div", {"hero"}, ""}}; std::set<std::string> seen3; auto heroResult = libhtmlpp::getCSSRules(*heroFrame, sheet, seen3, &heroAncestors); check(heroResult.properties["padding-top"] == "100px", "with its real (non-header) ancestor chain, the hero's frame keeps the general " "rule instead of the header-only one -- the actual bug fix"); } } // --- resolveCSSVariables --- std::cout << "=== resolveCSSVariables ===" << std::endl; { Loading