Help Viewer: Parse help markup to Pango markup... mostly.

The help parser now outputs a config rather than a vector of strings of
which some should be taken literally and others parsed as WML.
This commit is contained in:
Celtic Minstrel 2017-04-10 04:06:34 -04:00 committed by Celtic Minstrel
parent b6079891a8
commit bf4f02bdcf
6 changed files with 128 additions and 81 deletions

View file

@ -31,6 +31,8 @@
#include "help/help.hpp"
#include "help/help_impl.hpp"
#include "font/pango/escape.hpp"
#include "font/pango/hyperlink.hpp"
namespace gui2::dialogs
{
@ -79,6 +81,69 @@ void help_browser::add_topic(const std::string& topic_id, const std::string& top
topic_tree.add_node("topic", data).set_id(std::string(expands ? "+" : "-") + topic_id);
}
static std::string format_help_text(const config& cfg) {
std::stringstream ss;
for(auto item : cfg.all_children_range()) {
if(item.key == "text") {
ss << font::escape_text(item.cfg["text"]);
} else if(item.key == "ref") {
if(item.cfg["dst"].empty()) {
std::stringstream msg;
msg << "Ref markup must have dst attribute. Please submit a bug"
" report if you have not modified the game files yourself. Erroneous config: " << cfg;
throw help::parse_error(msg.str());
};
// TODO: Get the proper link shade from somewhere
ss << font::format_as_link(font::escape_text(item.cfg["text"]), color_t::from_hex_string("aaaa00"));
} else if(item.key == "img") {
if(item.cfg["src"].empty()) {
throw help::parse_error("Img markup must have src attribute.");
}
// For now, just output a placeholder so we know an image is supposed to be there.
ss << "[img]" << font::escape_text(item.cfg["src"]) << "[/img]";
} else if(item.key == "bold") {
if(item.cfg["text"].empty()) {
throw help::parse_error("Bold markup must have text attribute.");
}
ss << "<b>" << font::escape_text(item.cfg["text"]) << "</b>";
} else if(item.key == "italic") {
if(item.cfg["text"].empty()) {
throw help::parse_error("Italic markup must have text attribute.");
}
ss << "<i>" << font::escape_text(item.cfg["text"]) << "</i>";
} else if(item.key == "header") {
if(item.cfg["text"].empty()) {
throw help::parse_error("Header markup must have text attribute.");
}
ss << "<big>" << font::escape_text(item.cfg["text"]) << "</big>";
} else if(item.key == "jump") {
// Seems like this creates something invisible?
if(item.cfg["amount"].empty() && item.cfg["to"].empty()) {
throw help::parse_error("Jump markup must have either a to or an amount attribute.");
}
} else if(item.key == "format") {
if(item.cfg["text"].empty()) {
throw help::parse_error("Header markup must have text attribute.");
}
ss << "<span";
if(item.cfg.has_attribute("font_size")) {
ss << " size='" << item.cfg["font_size"].to_int(font::SIZE_NORMAL) << "'";
}
if(item.cfg.has_attribute("color")) {
ss << " color='" << font::string_to_color(item.cfg["color"]).to_hex_string() << "'";
}
if(item.cfg["bold"]) {
ss << " font_weight='bold'";
}
if(item.cfg["italic"]) {
ss << " font_style='italic'";
}
ss << '>' << font::escape_text(item.cfg["text"]) << "</span>";
}
}
return ss.str();
}
void help_browser::on_topic_select()
{
multi_page& topic_pages = find_widget<multi_page>(this, "topic_text_pages", false);
@ -112,7 +177,7 @@ void help_browser::on_topic_select()
widget_data data;
widget_item item;
item["label"] = utils::join(topic->text.parsed_text(), "");
item["label"] = format_help_text(topic->text.parsed_text());
data.emplace("topic_text", item);
parsed_pages_.emplace(topic_id, topic_pages.get_page_count());

View file

@ -290,10 +290,9 @@ void rich_label::set_label(const t_string& text)
text_dom_.clear();
links_.clear();
help::topic_text marked_up_text(text);
std::vector<std::string> parsed_text = marked_up_text.parsed_text();
config parsed_text = marked_up_text.parsed_text();
config* curr_item = nullptr;
optional_config_impl<config> child;
bool is_image = false;
bool floating = false;
@ -306,15 +305,10 @@ void rich_label::set_label(const t_string& text)
prev_blk_height_ = 0;
txt_height_ = 0;
for (size_t i = 0; i < parsed_text.size(); i++) {
bool last_entry = (i == parsed_text.size() - 1);
std::string line = parsed_text.at(i);
for(config::any_child tag : parsed_text.all_children_range()) {
config& child = tag.cfg;
if (!line.empty() && line.at(0) == '[') {
config cfg;
::read(cfg, line);
if ((child = cfg.optional_child("img"))) {
if(tag.key == "img") {
std::string name = child["src"];
floating = child["float"].to_bool();
@ -347,35 +341,35 @@ void rich_label::set_label(const t_string& text)
// }---------- TEXT TAGS -----------{
int tmp_h = get_text_size(*curr_item, w_ - x_).y;
if ((child = cfg.optional_child("ref"))) {
if(tag.key == "ref") {
add_link(*curr_item, child["text"], child["dst"], img_size.x);
is_image = false;
DBG_GUI_RL << "ref: dst=" << child["dst"];
} else if ((child = cfg.optional_child("bold")) || (child = cfg.optional_child("b"))) {
} else if(tag.key == "bold" || tag.key == "b") {
add_text_with_attribute(*curr_item, child["text"], "bold");
is_image = false;
DBG_GUI_RL << "bold: text=" << child["text"];
} else if ((child = cfg.optional_child("italic")) || (child = cfg.optional_child("i"))) {
} else if(tag.key == "italic" || tag.key == "i") {
add_text_with_attribute(*curr_item, child["text"], "italic");
is_image = false;
DBG_GUI_RL << "italic: text=" << child["text"];
} else if ((child = cfg.optional_child("underline")) || (child = cfg.optional_child("u"))) {
} else if(tag.key == "underline" || tag.key == "u") {
add_text_with_attribute(*curr_item, child["text"], "underline");
is_image = false;
DBG_GUI_RL << "u: text=" << child["text"];
} else if ((child = cfg.optional_child("header")) || (child = cfg.optional_child("h"))) {
} else if(tag.key == "header" || tag.key == "h") {
// Header starts in a new line
@ -398,7 +392,7 @@ void rich_label::set_label(const t_string& text)
DBG_GUI_RL << "h: text=" << child["text"];
} else if ((child = cfg.optional_child("span")) || (child = cfg.optional_child("format"))) {
} else if(tag.key == "span" || tag.key == "format") {
std::vector<std::string> attrs;
std::vector<std::string> attr_data;
@ -406,7 +400,7 @@ void rich_label::set_label(const t_string& text)
DBG_GUI_RL << "span/format: text=" << child["text"];
DBG_GUI_RL << "attributes:";
for (const auto& attr : child.value().attribute_range()) {
for (const auto& attr : child.attribute_range()) {
if (attr.first != "text") {
attrs.push_back(attr.first);
attr_data.push_back(attr.second);
@ -418,7 +412,7 @@ void rich_label::set_label(const t_string& text)
is_image = false;
// }---------- TABLE TAGS -----------{
} else if ((child = cfg.optional_child("table"))) {
} else if(tag.key == "table") {
in_table = true;
@ -439,7 +433,7 @@ void rich_label::set_label(const t_string& text)
DBG_GUI_RL << "start table : " << "col=" << columns;
DBG_GUI_RL << "col_width : " << col_width;
} else if (cfg.optional_child("jump")) {
} else if(tag.key == "jump") {
if (col_width > 0) {
@ -457,7 +451,7 @@ void rich_label::set_label(const t_string& text)
}
}
} else if (cfg.optional_child("break") || cfg.optional_child("br")) {
} else if(tag.key == "break" || tag.key == "br") {
if (in_table) {
@ -480,7 +474,7 @@ void rich_label::set_label(const t_string& text)
new_text_block = true;
}
} else if (cfg.optional_child("endtable")) {
} else if(tag.key == "endtable") {
DBG_GUI_RL << "end table: " << max_col_height;
max_col_height = std::max(max_col_height, txt_height_);
@ -494,9 +488,7 @@ void rich_label::set_label(const t_string& text)
max_col_height = 0;
txt_height_ = 0;
if (!last_entry) {
new_text_block = true;
}
in_table = false;
}
@ -512,7 +504,8 @@ void rich_label::set_label(const t_string& text)
}
}
} else if (!line.empty()) {
if (tag.key == "text") {
std::string line = child["text"];
DBG_GUI_RL << "text: text=" << line.substr(1, 20) << "...";
// Start the text in a new paragraph if a newline follows after an image
@ -601,9 +594,9 @@ void rich_label::set_label(const t_string& text)
DBG_GUI_RL << "Prev block height: " << prev_blk_height_ << " Current text block height: " << txt_height_;
DBG_GUI_RL << "Height: " << h_;
h_ = txt_height_ + prev_blk_height_;
DBG_GUI_RL << "-----------";
} // for loop ends
// reset all variables to zero, otherwise they grow infinitely
if (last_entry) {
if (static_cast<unsigned>(img_size.y) > h_) {
h_ = img_size.y;
}
@ -613,11 +606,7 @@ void rich_label::set_label(const t_string& text)
default_text_config(&break_cfg);
break_cfg["text"] = " ";
break_cfg["actions"] = "([set_var('pos_x', 0), set_var('pos_y', 0), set_var('img_x', 0), set_var('img_y', 0)])";
}
DBG_GUI_RL << "-----------";
} // for loop ends
} // function ends

View file

@ -33,6 +33,7 @@
#include "units/race.hpp" // for unit_race, etc
#include "resources.hpp" // for tod_manager, config_manager
#include "sdl/surface.hpp" // for surface
#include "serialization/parser.hpp"
#include "serialization/string_utils.hpp" // for split, quoted_split, etc
#include "serialization/unicode_cast.hpp" // for unicode_cast
#include "serialization/utf8_exception.hpp" // for char_t, etc
@ -382,7 +383,7 @@ topic_text& topic_text::operator=(std::shared_ptr<topic_generator> g)
return *this;
}
const std::vector<std::string>& topic_text::parsed_text() const
const config& topic_text::parsed_text() const
{
if (generator_) {
parsed_text_ = parse_text((*generator_)());
@ -1331,9 +1332,9 @@ section *find_section(section &sec, const std::string &id)
return const_cast<section *>(find_section(const_cast<const section &>(sec), id));
}
std::vector<std::string> parse_text(const std::string &text)
config parse_text(const std::string &text)
{
std::vector<std::string> res;
config res;
bool last_char_escape = false;
bool found_slash = false;
bool in_quotes = false;
@ -1351,7 +1352,7 @@ std::vector<std::string> parse_text(const std::string &text)
if (last_char_escape) {
ss << c;
} else {
res.push_back(ss.str());
res.add_child("text", config("text", ss.str()));
ss.str("");
state = ELEMENT_NAME;
}
@ -1382,7 +1383,7 @@ std::vector<std::string> parse_text(const std::string &text)
if (found_slash) {
// empty tag
res.push_back(convert_to_wml(element_name, attrs));
s.str(convert_to_wml(element_name, attrs));
found_slash = false;
pos = text.find(">", pos);
} else {
@ -1395,12 +1396,21 @@ std::vector<std::string> parse_text(const std::string &text)
msg << "Unterminated element: " << element_name;
throw parse_error(msg.str());
}
s.str("");
const std::string contents = attrs + " " + text.substr(pos + 1, end_pos - pos - 1);
const std::string element = convert_to_wml(element_name, contents);
res.push_back(element);
s.str(element);
pos = end_pos + end_element_name.size() - 1;
}
s.seekg(0);
try {
config cfg;
read(cfg, s);
res.append_children(cfg);
} catch(config::error& e) {
std::stringstream msg;
msg << "Error when parsing help markup as WML: '" << e.message << "'";
throw parse_error(msg.str());
}
state = OTHER;
} else {
if (found_slash) {
@ -1422,7 +1432,7 @@ std::vector<std::string> parse_text(const std::string &text)
}
if (!ss.str().empty()) {
// Add the last string.
res.push_back(ss.str());
res.add_child("text", config("text", ss.str()));
}
return res;
}

View file

@ -46,9 +46,9 @@
#include <utility> // for pair, make_pair
#include <vector> // for vector, etc
#include <boost/logic/tribool.hpp>
#include "config.hpp"
class game_config_view;
class config;
class unit_type;
class terrain_type_data;
@ -81,7 +81,7 @@ public:
*/
class topic_text
{
mutable std::vector< std::string > parsed_text_;
mutable config parsed_text_;
mutable std::shared_ptr<topic_generator> generator_;
public:
topic_text() = default;
@ -105,7 +105,7 @@ public:
topic_text& operator=(const topic_text& t) = default;
topic_text& operator=(std::shared_ptr<topic_generator> g);
const std::vector<std::string>& parsed_text() const;
const config& parsed_text() const;
};
/** A topic contains a title, an id and some text. */
@ -304,13 +304,10 @@ const topic *find_topic(const section &sec, const std::string &id);
const section *find_section(const section &sec, const std::string &id);
section *find_section(section &sec, const std::string &id);
/**
* Parse a xml style marked up text string. Return a vector with
* the different parts of the text. Each markup item and the text
* between markups are separate parts. Each line of returned vector
* is valid WML.
*/
std::vector<std::string> parse_text(const std::string &text);
/// Parse a xml style marked up text string. Return a config with the different parts of the
/// text. Each markup item is a separate part while the text between
/// markups are separate parts.
config parse_text(const std::string &text);
/**
* Convert the the text between start and end xml tags for element_name to

View file

@ -122,41 +122,22 @@ void help_text_area::set_items()
down_one_line();
}
// Parse and add the text.
const std::vector<std::string>& parsed_items = shown_topic_->text.parsed_text();
std::vector<std::string>::const_iterator it;
for (it = parsed_items.begin(); it != parsed_items.end(); ++it) {
if (!(*it).empty() && (*it)[0] == '[') {
// Should be parsed as WML.
try {
config cfg;
std::istringstream stream(*it);
read(cfg, stream);
const config& parsed_items = shown_topic_->text.parsed_text();
for(auto item : parsed_items.all_children_range()) {
#define TRY(name) do { \
if (auto child = cfg.optional_child(#name)) \
handle_##name##_cfg(*child); \
} while (0)
TRY(ref);
TRY(img);
TRY(bold);
TRY(italic);
TRY(header);
TRY(jump);
TRY(format);
if (auto child = item.cfg.optional_child(#name)) \
handle_##name##_cfg(child.value()); \
} while (0)
TRY(text);
TRY(ref);
TRY(img);
TRY(bold);
TRY(italic);
TRY(header);
TRY(jump);
TRY(format);
#undef TRY
}
catch (config::error& e) {
std::stringstream msg;
msg << "Error when parsing help markup as WML: '" << e.message << "'";
throw parse_error(msg.str());
}
}
else {
add_text_item(*it);
}
}
down_one_line(); // End the last line.
int h = height();
@ -299,6 +280,10 @@ void help_text_area::handle_format_cfg(const config &cfg)
add_text_item(text, "", false, font_size, bold, italic, color);
}
void help_text_area::handle_text_cfg(const config& cfg) {
add_text_item(cfg["text"]);
}
void help_text_area::add_text_item(const std::string& text, const std::string& ref_dst,
bool broken_link, int _font_size, bool bold, bool italic,
color_t text_color

View file

@ -110,6 +110,7 @@ private:
void handle_header_cfg(const config &cfg);
void handle_jump_cfg(const config &cfg);
void handle_format_cfg(const config &cfg);
void handle_text_cfg(const config &cfg);
void draw_contents();