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:
parent
b6079891a8
commit
bf4f02bdcf
6 changed files with 128 additions and 81 deletions
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue