Loading src/html.cpp +108 −0 Original line number Diff line number Diff line Loading @@ -639,6 +639,21 @@ void libhtmlpp::HtmlString::_buildTree() { p = base + i; continue; } if (starts_with_ci(p, end, "<style")) { // Same raw-text rationale as <script> above: CSS // content can contain '<'/'>' (child-combinator // selectors, stray markup pasted into a comment, // duplicate/unclosed <style> tags from a page // builder concatenating snippets, ...) that the // ordinary tag-tokenizing path below would // misinterpret as nested markup, corrupting // everything parsed after it. add_element_node(&lastEl); size_t i = static_cast<size_t>(p - base); i = HtmlElement::parseStyleElement(_Data, lastEl->element, i, lastEl->terminator); p = base + i; continue; } } else if (c1 == 't') { if (starts_with_ci(p, end, "<textarea")) { add_element_node(&lastEl); Loading Loading @@ -1120,6 +1135,99 @@ size_t libhtmlpp::HtmlElement::parseElement( return foundClose ? close + 1 : close; } size_t libhtmlpp::HtmlElement::parseStyleElement( const std::vector<char>& in, std::unique_ptr<Element>& el, size_t start, bool& termination ){ termination = false; el = std::make_unique<HtmlElement>("style"); auto* self = static_cast<HtmlElement*>(el.get()); size_t i = start; if (i >= in.size() || in[i] != HTMLTAG_OPEN) { return start; } auto iequals = [](char a, char b) { return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b)); }; auto match_ci = [&](size_t pos, const char* k) -> bool { for (size_t j = 0; k[j]; ++j) { if (pos + j >= in.size() || !iequals(in[pos + j], k[j])) { return false; } } return true; }; ++i; // consume '<' while (i < in.size() && std::isspace(static_cast<unsigned char>(in[i]))) ++i; const char* tag_keyword = "style"; size_t keyword_len = std::char_traits<char>::length(tag_keyword); if (i + keyword_len >= in.size() || !match_ci(i, tag_keyword)) { // Not actually a <style -- skip to the next '>' like the other // dedicated parseElement variants do for their own mismatched case. while (i < in.size() && in[i] != HTMLTAG_CLOSE) ++i; if (i < in.size()) ++i; return i; } i += keyword_len; // consume "style" // Find the opening tag's own '>' (attributes only -- style's opening // tag is ordinary markup, just its content that needs raw-text care). while (i < in.size() && in[i] != HTMLTAG_CLOSE) ++i; if (i > start && i < in.size() && in[i] == HTMLTAG_CLOSE) { std::vector<char> raw_tag_data(in.begin() + start, in.begin() + i + 1); self->_serialelize(raw_tag_data); } if (i >= in.size() || in[i] != HTMLTAG_CLOSE) { // Opening tag itself was never closed (e.g. EOF mid-attribute-list). return i; } ++i; // consume '>' size_t content_begin = i; // Raw-text scan for the literal closing sequence: </style -- everything // in between, including any '<'/'>' a CSS selector/comment happens to // contain, is opaque content, exactly like <script>'s own handling. for (; i < in.size(); ++i) { if (in[i] == HTMLTAG_OPEN && match_ci(i, "</style")) { if (i > content_begin) { std::string cssText(in.begin() + content_begin, in.begin() + i); TextElement textEl(cssText); self->appendChild(&textEl); } size_t closing_tag_end_pos = i + keyword_len + 2; // +2 for '</' while (closing_tag_end_pos < in.size() && in[closing_tag_end_pos] != HTMLTAG_CLOSE) { ++closing_tag_end_pos; } if (closing_tag_end_pos < in.size() && in[closing_tag_end_pos] == HTMLTAG_CLOSE) { return closing_tag_end_pos + 1; } return closing_tag_end_pos; } } // No closing </style> before EOF -- capture whatever's left, same // best-effort fallback ScriptElement::parseElement uses. if (in.size() > content_begin) { std::string cssText(in.begin() + content_begin, in.end()); TextElement textEl(cssText); self->appendChild(&textEl); } return i; } namespace libhtmlpp { void _copy(libhtmlpp::Element *dest,const libhtmlpp::Element *src){ Loading src/html.h +11 −0 Original line number Diff line number Diff line Loading @@ -166,6 +166,17 @@ namespace libhtmlpp { void remove(Element* el); static size_t parseElement(const std::vector<char> &in,std::unique_ptr<libhtmlpp::Element> &el,size_t start,bool &termination); /** Parses a <style> tag the same "raw text until literal </style>" * way ScriptElement::parseElement parses <script> -- CSS content * can contain '<'/'>' (child-combinator selectors, stray markup * pasted into a comment, etc.) that the ordinary tag-tokenizing * path would misinterpret as nested markup. Unlike script/svg/ * textarea, this deliberately still produces a plain HtmlElement * (tag "style") with one TextElement child holding the raw CSS, * not a dedicated element type -- callers (e.g. blogi's htmlimport) * already read style content that way, keyed off the tag name. */ static size_t parseStyleElement(const std::vector<char> &in,std::unique_ptr<libhtmlpp::Element> &el,size_t start,bool &termination); protected: std::unique_ptr<Element> _childElement=nullptr; Loading Loading
src/html.cpp +108 −0 Original line number Diff line number Diff line Loading @@ -639,6 +639,21 @@ void libhtmlpp::HtmlString::_buildTree() { p = base + i; continue; } if (starts_with_ci(p, end, "<style")) { // Same raw-text rationale as <script> above: CSS // content can contain '<'/'>' (child-combinator // selectors, stray markup pasted into a comment, // duplicate/unclosed <style> tags from a page // builder concatenating snippets, ...) that the // ordinary tag-tokenizing path below would // misinterpret as nested markup, corrupting // everything parsed after it. add_element_node(&lastEl); size_t i = static_cast<size_t>(p - base); i = HtmlElement::parseStyleElement(_Data, lastEl->element, i, lastEl->terminator); p = base + i; continue; } } else if (c1 == 't') { if (starts_with_ci(p, end, "<textarea")) { add_element_node(&lastEl); Loading Loading @@ -1120,6 +1135,99 @@ size_t libhtmlpp::HtmlElement::parseElement( return foundClose ? close + 1 : close; } size_t libhtmlpp::HtmlElement::parseStyleElement( const std::vector<char>& in, std::unique_ptr<Element>& el, size_t start, bool& termination ){ termination = false; el = std::make_unique<HtmlElement>("style"); auto* self = static_cast<HtmlElement*>(el.get()); size_t i = start; if (i >= in.size() || in[i] != HTMLTAG_OPEN) { return start; } auto iequals = [](char a, char b) { return std::tolower(static_cast<unsigned char>(a)) == std::tolower(static_cast<unsigned char>(b)); }; auto match_ci = [&](size_t pos, const char* k) -> bool { for (size_t j = 0; k[j]; ++j) { if (pos + j >= in.size() || !iequals(in[pos + j], k[j])) { return false; } } return true; }; ++i; // consume '<' while (i < in.size() && std::isspace(static_cast<unsigned char>(in[i]))) ++i; const char* tag_keyword = "style"; size_t keyword_len = std::char_traits<char>::length(tag_keyword); if (i + keyword_len >= in.size() || !match_ci(i, tag_keyword)) { // Not actually a <style -- skip to the next '>' like the other // dedicated parseElement variants do for their own mismatched case. while (i < in.size() && in[i] != HTMLTAG_CLOSE) ++i; if (i < in.size()) ++i; return i; } i += keyword_len; // consume "style" // Find the opening tag's own '>' (attributes only -- style's opening // tag is ordinary markup, just its content that needs raw-text care). while (i < in.size() && in[i] != HTMLTAG_CLOSE) ++i; if (i > start && i < in.size() && in[i] == HTMLTAG_CLOSE) { std::vector<char> raw_tag_data(in.begin() + start, in.begin() + i + 1); self->_serialelize(raw_tag_data); } if (i >= in.size() || in[i] != HTMLTAG_CLOSE) { // Opening tag itself was never closed (e.g. EOF mid-attribute-list). return i; } ++i; // consume '>' size_t content_begin = i; // Raw-text scan for the literal closing sequence: </style -- everything // in between, including any '<'/'>' a CSS selector/comment happens to // contain, is opaque content, exactly like <script>'s own handling. for (; i < in.size(); ++i) { if (in[i] == HTMLTAG_OPEN && match_ci(i, "</style")) { if (i > content_begin) { std::string cssText(in.begin() + content_begin, in.begin() + i); TextElement textEl(cssText); self->appendChild(&textEl); } size_t closing_tag_end_pos = i + keyword_len + 2; // +2 for '</' while (closing_tag_end_pos < in.size() && in[closing_tag_end_pos] != HTMLTAG_CLOSE) { ++closing_tag_end_pos; } if (closing_tag_end_pos < in.size() && in[closing_tag_end_pos] == HTMLTAG_CLOSE) { return closing_tag_end_pos + 1; } return closing_tag_end_pos; } } // No closing </style> before EOF -- capture whatever's left, same // best-effort fallback ScriptElement::parseElement uses. if (in.size() > content_begin) { std::string cssText(in.begin() + content_begin, in.end()); TextElement textEl(cssText); self->appendChild(&textEl); } return i; } namespace libhtmlpp { void _copy(libhtmlpp::Element *dest,const libhtmlpp::Element *src){ Loading
src/html.h +11 −0 Original line number Diff line number Diff line Loading @@ -166,6 +166,17 @@ namespace libhtmlpp { void remove(Element* el); static size_t parseElement(const std::vector<char> &in,std::unique_ptr<libhtmlpp::Element> &el,size_t start,bool &termination); /** Parses a <style> tag the same "raw text until literal </style>" * way ScriptElement::parseElement parses <script> -- CSS content * can contain '<'/'>' (child-combinator selectors, stray markup * pasted into a comment, etc.) that the ordinary tag-tokenizing * path would misinterpret as nested markup. Unlike script/svg/ * textarea, this deliberately still produces a plain HtmlElement * (tag "style") with one TextElement child holding the raw CSS, * not a dedicated element type -- callers (e.g. blogi's htmlimport) * already read style content that way, keyed off the tag name. */ static size_t parseStyleElement(const std::vector<char> &in,std::unique_ptr<libhtmlpp::Element> &el,size_t start,bool &termination); protected: std::unique_ptr<Element> _childElement=nullptr; Loading