#include "stflrichtext.h" #include #include "utils.h" namespace newsboat { StflRichText::StflRichText(std::string&& text, std::map&& style_tags) : text(std::move(text)) , style_tags(std::move(style_tags)) { } StflRichText operator+(StflRichText left, const StflRichText& right) { auto text = left.text + right.text; auto tags = left.style_tags; for (const auto& tag_position : right.style_tags) { tags[tag_position.first + left.text.size()] = tag_position.second; } return StflRichText(std::move(text), std::move(tags)); } StflRichText StflRichText::from_plaintext(std::string text) { text = utils::quote_for_stfl(text); return from_quoted(text); } StflRichText StflRichText::from_quoted(std::string text) { auto style_tags = extract_style_tags(text); return StflRichText(std::move(text), std::move(style_tags)); } void StflRichText::apply_style_tag(const std::string& tag, size_t start, size_t end) { merge_style_tag(style_tags, tag, start, end); } std::string StflRichText::plaintext() const { return text; } std::string StflRichText::stfl_quoted() const { return insert_style_tags(text, style_tags); } std::map StflRichText::extract_style_tags(std::string& str) { std::map tags; size_t pos = 0; while (pos < str.size()) { auto tag_start = str.find_first_of("<>", pos); if (tag_start == std::string::npos) { break; } if (str[tag_start] == '>') { // Keep unmatched '>' (stfl way of encoding a literal '>') pos = tag_start + 1; continue; } auto tag_end = str.find_first_of("<>", tag_start + 1); if (tag_end == std::string::npos) { break; } if (str[tag_end] == '<') { // First '<' bracket is unmatched, ignoring it pos = tag_start + 1; continue; } if (tag_end - tag_start == 1) { // Convert "<>" into "<" (stfl way of encoding a literal '<') str.erase(tag_end, 1); pos = tag_start + 1; continue; } tags[tag_start] = str.substr(tag_start, tag_end - tag_start + 1); str.erase(tag_start, tag_end - tag_start + 1); pos = tag_start; } return tags; } void StflRichText::merge_style_tag(std::map& tags, const std::string& tag, size_t start, size_t end) { if (end <= start) { return; } // Find the latest tag occurring before `end`. // It is important that looping executes in ascending order of location. std::string latest_tag = ""; for (const auto& location_tag : tags) { size_t location = location_tag.first; if (location > end) { break; } latest_tag = location_tag.second; } tags[start] = tag; tags[end] = latest_tag; // Remove any old tags between the start and end marker for (auto it = tags.begin(); it != tags.end(); ) { if (it->first > start && it->first < end) { it = tags.erase(it); } else { ++it; } } } std::string StflRichText::insert_style_tags(const std::string& text, const std::map& style_tags) { auto str = text; auto tags = style_tags; // Expand "<" into "<>" (reverse of what happened in extract_style_tags() size_t pos = 0; while (pos < str.size()) { auto bracket = str.find_first_of("<", pos); if (bracket == std::string::npos) { break; } pos = bracket + 1; // Add to strings in the `tags` map so we don't have to shift all the positions in that map // (would be necessary if inserting directly into `str`) tags[pos] = ">" + tags[pos]; } for (auto it = tags.rbegin(); it != tags.rend(); ++it) { if (it->first > str.length()) { // Ignore tags outside of string continue; } str.insert(it->first, it->second); } return str; } }