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
parent 02b472b05c
commit 3ddd5670d9
5 changed files with 105 additions and 44 deletions

View file

@ -37,6 +37,8 @@
#include "help/help.hpp"
#include "help/help_impl.hpp"
#include "font/pango/escape.hpp"
#include "font/pango/hyperlink.hpp"
namespace gui2
{
@ -85,6 +87,69 @@ void help_browser::add_topic(window& window, const std::string& topic_id, const
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"]), "#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='" << help::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(window& window)
{
multi_page& topic_pages = find_widget<multi_page>(&window, "topic_text_pages", false);
@ -118,7 +183,7 @@ void help_browser::on_topic_select(window& window)
std::map<std::string, string_map> data;
string_map 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

@ -32,6 +32,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
@ -44,6 +45,7 @@
#include "units/types.hpp" // for unit_type, unit_type_data, etc
#include "serialization/unicode.hpp" // for iterator
#include "color.hpp"
#include "config_assign.hpp"
#include <cassert> // for assert
#include <algorithm> // for sort, find, transform, etc
@ -344,7 +346,7 @@ topic_text &topic_text::operator=(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_)());
@ -1168,9 +1170,9 @@ const section *find_section(const section &sec, const std::string &id)
return nullptr;
}
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;
const char escape_char = '\\';
std::stringstream ss;
@ -1188,7 +1190,7 @@ std::vector<std::string> parse_text(const std::string &text)
ss << c;
}
else {
res.push_back(ss.str());
res.add_child("text", config_of("text", ss.str()));
ss.str("");
state = ELEMENT_NAME;
}
@ -1215,10 +1217,18 @@ 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 = text.substr(pos + 1, end_pos - pos - 1);
const std::string element = convert_to_wml(element_name, contents);
res.push_back(element);
s.str(convert_to_wml(element_name, contents));
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());
}
pos = end_pos + end_element_name.size() - 1;
state = OTHER;
}
@ -1236,7 +1246,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_of("text", ss.str()));
}
return res;
}

View file

@ -80,7 +80,7 @@ public:
/// contained in generator_.
class topic_text
{
mutable std::vector< std::string > parsed_text_;
mutable config parsed_text_;
mutable topic_generator *generator_;
public:
~topic_text();
@ -104,7 +104,7 @@ public:
topic_text &operator=(topic_generator *g);
topic_text(const topic_text& t);
const std::vector<std::string>& parsed_text() const;
const config& parsed_text() const;
};
/// A topic contains a title, an id and some text.
@ -287,7 +287,7 @@ const section *find_section(const section &sec, const std::string &id);
/// Parse a text string. Return a vector with the different parts of the
/// text. Each markup item is a separate part while the text between
/// markups are separate parts.
std::vector<std::string> parse_text(const std::string &text);
config parse_text(const std::string &text);
/// Convert the contents to wml attributes, surrounded within
/// [element_name]...[/element_name]. Return the resulting WML.

View file

@ -113,41 +113,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 (config &child = cfg.child(#name)) \
handle_##name##_cfg(child); \
} while (0)
TRY(ref);
TRY(img);
TRY(bold);
TRY(italic);
TRY(header);
TRY(jump);
TRY(format);
if (config &child = item.cfg.child(#name)) \
handle_##name##_cfg(child); \
} 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();
@ -290,6 +271,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

@ -104,6 +104,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();