1 #include "VarText.h"
2
3 #include "../universe/Universe.h"
4 #include "../universe/ShipDesign.h"
5 #include "../universe/System.h"
6 #include "../universe/Enums.h"
7 #include "../Empire/Empire.h"
8 #include "i18n.h"
9 #include "Logger.h"
10 #include "AppInterface.h"
11
12 #include <boost/xpressive/xpressive.hpp>
13
14 #include <functional>
15 #include <map>
16
17 namespace xpr = boost::xpressive;
18
19 class Tech;
20 class BuildingType;
21 class Special;
22 class Species;
23 class FieldType;
24 class ShipHull;
25 class ShipPart;
26 const Tech* GetTech(const std::string& name);
27 const BuildingType* GetBuildingType(const std::string& name);
28 const Special* GetSpecial(const std::string& name);
29 const Species* GetSpecies(const std::string& name);
30 const FieldType* GetFieldType(const std::string& name);
31 const ShipHull* GetShipHull(const std::string& name);
32 const ShipPart* GetShipPart(const std::string& name);
33
34 namespace {
35 //! Return @p content surrounded by the given @p tags.
36 //!
37 //! @param content
38 //! The text that should be wrapped into @p tags.
39 //! @param tag
40 //! The tags that should be wrapping the given @p content. The tag
41 //! shouldn't contain whitespace.
42 //! @param data
43 //! Additional data assigned to the @p tag.
44 //!
45 //! @return
46 //! The tagged content.
WithTags(const std::string & content,const std::string & tag,const std::string & data)47 std::string WithTags(const std::string& content, const std::string& tag, const std::string& data) {
48 std::string open_tag = "<" + tag + " " + data + ">";
49 std::string close_tag = "</" + tag + ">";
50 return open_tag + content + close_tag;
51 }
52
53 //! Function signature of tag substitution functions.
54 //!
55 //! @param data
56 //! Data values The signature of functions that generate substitution
57 //! strings for tags.
58 typedef std::function<boost::optional<std::string> (const std::string& data)> TagString;
59
60 //! Get string substitute for a tag that is a universe object
UniverseObjectString(const std::string & data,const std::string & tag)61 boost::optional<std::string> UniverseObjectString(const std::string& data, const std::string& tag) {
62 int object_id = INVALID_OBJECT_ID;
63 try {
64 object_id = boost::lexical_cast<int>(data);
65 } catch (...) {
66 return boost::none;
67 }
68 auto obj = Objects().get(object_id);
69 if (!obj)
70 return boost::none;
71
72 return WithTags(GetVisibleObjectName(obj), tag, data);
73 }
74
75 //! Returns substitution string for a predefined ship design tag
PredefinedShipDesignString(const std::string & data)76 boost::optional<std::string> PredefinedShipDesignString(const std::string& data) {
77 const ShipDesign* design = GetPredefinedShipDesign(data);
78 if (!design)
79 return boost::none;
80
81 return WithTags(design->Name(), VarText::PREDEFINED_DESIGN_TAG, data);
82 }
83
MeterTypeString(const std::string & data)84 boost::optional<std::string> MeterTypeString(const std::string& data) {
85 boost::optional<std::string> retval = boost::none;
86 // validate data
87 MeterType meter_type = INVALID_METER_TYPE;
88 std::istringstream data_ss(data);
89 data_ss >> meter_type;
90
91 if (meter_type > INVALID_METER_TYPE && meter_type < NUM_METER_TYPES) {
92 retval = boost::lexical_cast<std::string>(meter_type);
93 if (UserStringExists(*retval))
94 retval = WithTags(UserString(*retval), VarText::METER_TYPE_TAG, *retval);
95 }
96
97 return retval;
98 }
99
100 //! Returns substitution string for a ship design tag
101 template <typename T,T* (*GetByID)(int)>
IDString(const std::string & data,const std::string & tag)102 boost::optional<std::string> IDString(const std::string& data, const std::string& tag) {
103 int id{};
104 try {
105 id = boost::lexical_cast<int>(data);
106 } catch (...) {
107 return boost::none;
108 }
109 T* object = GetByID(id);
110 if (!object) {
111 if (std::is_same<T, const ShipDesign>::value)
112 return UserString("FW_UNKNOWN_DESIGN_NAME");
113 else
114 return boost::none;
115 }
116
117 return WithTags(object->Name(), tag, data);
118 }
119
120 //! Returns substitution string for an empire tag
121 //! Interprets value of data as a name.
122 //! Returns translation of name, if Get says
123 //! that a thing by that name exists, otherwise boost::none.
124 template <typename T,const T* (*GetByName)(const std::string&)>
NameString(const std::string & data,const std::string & tag)125 boost::optional<std::string> NameString(const std::string& data, const std::string& tag) {
126 if (!GetByName(data))
127 return boost::none;
128 return WithTags(UserString(data), tag, data);
129 }
130
131 //! Global substitution map, wrapped in a function to avoid initialization order issues
SubstitutionMap()132 const std::map<std::string, TagString>& SubstitutionMap() {
133 static std::map<std::string, TagString> substitute_map{
134 {VarText::TEXT_TAG, [](const std::string& data) -> boost::optional<std::string>
135 { return UserString(data); }},
136 {VarText::RAW_TEXT_TAG, [](const std::string& data) -> boost::optional<std::string>
137 { return data; }},
138 {VarText::PLANET_ID_TAG, [](const std::string& data)
139 { return UniverseObjectString(data, VarText::PLANET_ID_TAG); }},
140 {VarText::SYSTEM_ID_TAG, [](const std::string& data)
141 { return UniverseObjectString(data, VarText::SYSTEM_ID_TAG); }},
142 {VarText::SHIP_ID_TAG, [](const std::string& data)
143 { return UniverseObjectString(data, VarText::SHIP_ID_TAG); }},
144 {VarText::FLEET_ID_TAG, [](const std::string& data)
145 { return UniverseObjectString(data, VarText::FLEET_ID_TAG); }},
146 {VarText::BUILDING_ID_TAG, [](const std::string& data)
147 { return UniverseObjectString(data, VarText::BUILDING_ID_TAG); }},
148 {VarText::FIELD_ID_TAG, [](const std::string& data)
149 { return UniverseObjectString(data, VarText::FIELD_ID_TAG); }},
150 {VarText::COMBAT_ID_TAG, [](const std::string& data) -> boost::optional<std::string>
151 { return WithTags(UserString("COMBAT"), VarText::COMBAT_ID_TAG, data); }},
152 {VarText::TECH_TAG, [](const std::string& data)
153 { return NameString<Tech, GetTech>(data, VarText::TECH_TAG); }},
154 {VarText::BUILDING_TYPE_TAG, [](const std::string& data)
155 { return NameString<BuildingType, GetBuildingType>(data, VarText::BUILDING_TYPE_TAG); }},
156 {VarText::SHIP_HULL_TAG, [](const std::string& data)
157 { return NameString<ShipHull, GetShipHull>(data, VarText::SHIP_HULL_TAG); }},
158 {VarText::SHIP_PART_TAG, [](const std::string& data)
159 { return NameString<ShipPart, GetShipPart>(data, VarText::SHIP_PART_TAG); }},
160 {VarText::SPECIAL_TAG, [](const std::string& data)
161 { return NameString<Special, GetSpecial>(data, VarText::SPECIAL_TAG); }},
162 {VarText::SPECIES_TAG, [](const std::string& data)
163 { return NameString<Species, GetSpecies>(data, VarText::SPECIES_TAG); }},
164 {VarText::FIELD_TYPE_TAG, [](const std::string& data)
165 { return NameString<FieldType, GetFieldType>(data, VarText::FIELD_TYPE_TAG); }},
166 {VarText::METER_TYPE_TAG, MeterTypeString},
167 {VarText::DESIGN_ID_TAG, [](const std::string& data)
168 { return IDString<const ShipDesign, GetShipDesign>(data, VarText::DESIGN_ID_TAG); }},
169 {VarText::PREDEFINED_DESIGN_TAG, PredefinedShipDesignString},
170 {VarText::EMPIRE_ID_TAG, [](const std::string& data)
171 { return IDString<Empire, GetEmpire>(data, VarText::EMPIRE_ID_TAG); }},
172 };
173
174 return substitute_map;
175 }
176
177
178 //! Looks up the given match in the Universe and returns the Universe
179 //! entities value.
180 struct Substitute {
Substitute__anon69262a300111::Substitute181 Substitute(const std::map<std::string, std::string>& variables,
182 bool& valid) :
183 m_variables(variables),
184 m_valid(valid)
185 {}
186
operator ()__anon69262a300111::Substitute187 std::string operator()(xpr::smatch const& match) const {
188 // Labelled variables have the form %tag:label%, unlabelled are just %tag%
189 std::string tag = match[1];
190 // Use the label value. When missing, use the tag submatch as label instead.
191 std::string label = match[match[2].matched ? 2 : 1];
192
193 // look up child
194 auto elem = m_variables.find(label);
195 if (m_variables.end() == elem) {
196 ErrorLogger() << "Substitute::operator(): No value found for label: " << label << " from token: " << match.str();
197 m_valid = false;
198 return UserString("ERROR");
199 }
200
201 auto substituter = SubstitutionMap().find(tag);
202 if (substituter != SubstitutionMap().end()) {
203 if (auto substitution = substituter->second(elem->second)) {
204 return *substitution;
205 }
206 }
207
208 ErrorLogger() << "Substitute::operator(): No substitution executed for tag: " << tag << " from token: " << match.str();
209 m_valid = false;
210 return UserString("ERROR");
211 }
212
213 const std::map<std::string, std::string>& m_variables;
214 bool& m_valid;
215 };
216 }
217
218
219 const std::string VarText::TEXT_TAG = "text";
220 const std::string VarText::RAW_TEXT_TAG = "rawtext";
221
222 const std::string VarText::PLANET_ID_TAG = "planet";
223 const std::string VarText::SYSTEM_ID_TAG = "system";
224 const std::string VarText::SHIP_ID_TAG = "ship";
225 const std::string VarText::FLEET_ID_TAG = "fleet";
226 const std::string VarText::BUILDING_ID_TAG = "building";
227 const std::string VarText::FIELD_ID_TAG = "field";
228
229 const std::string VarText::COMBAT_ID_TAG = "combat";
230
231 const std::string VarText::EMPIRE_ID_TAG = "empire";
232 const std::string VarText::DESIGN_ID_TAG = "shipdesign";
233 const std::string VarText::PREDEFINED_DESIGN_TAG = "predefinedshipdesign";
234
235 const std::string VarText::TECH_TAG = "tech";
236 const std::string VarText::BUILDING_TYPE_TAG = "buildingtype";
237 const std::string VarText::SPECIAL_TAG = "special";
238 const std::string VarText::SHIP_HULL_TAG = "shiphull";
239 const std::string VarText::SHIP_PART_TAG = "shippart";
240 const std::string VarText::SPECIES_TAG = "species";
241 const std::string VarText::FIELD_TYPE_TAG = "fieldtype";
242 const std::string VarText::METER_TYPE_TAG = "metertype";
243
244
VarText()245 VarText::VarText()
246 {}
247
VarText(const std::string & template_string,bool stringtable_lookup)248 VarText::VarText(const std::string& template_string, bool stringtable_lookup/* = true*/) :
249 m_template_string(template_string),
250 m_stringtable_lookup_flag(stringtable_lookup)
251 {}
252
GetText() const253 const std::string& VarText::GetText() const {
254 if (m_text.empty())
255 GenerateVarText();
256 return m_text;
257 }
258
Validate() const259 bool VarText::Validate() const {
260 if (m_text.empty())
261 GenerateVarText();
262 return m_validated;
263 }
264
SetTemplateString(const std::string & template_string,bool stringtable_lookup)265 void VarText::SetTemplateString(const std::string& template_string, bool stringtable_lookup/* = true*/) {
266 m_template_string = template_string;
267 m_stringtable_lookup_flag = stringtable_lookup;
268 }
269
GetVariableTags() const270 std::vector<std::string> VarText::GetVariableTags() const {
271 std::vector<std::string> retval;
272 for (const auto& variable : m_variables)
273 retval.push_back(variable.first);
274 return retval;
275 }
276
AddVariable(const std::string & tag,const std::string & data)277 void VarText::AddVariable(const std::string& tag, const std::string& data)
278 { m_variables[tag] = data; }
279
GenerateVarText() const280 void VarText::GenerateVarText() const {
281 // generate a string complete with substituted variables and hyperlinks
282 // the procedure here is to replace any tokens within %% with variables of
283 // the same name in the SitRep XML data
284 m_text.clear();
285 m_validated = true;
286 if (m_template_string.empty())
287 return;
288
289 // get string into which to substitute variables
290 std::string template_str = m_stringtable_lookup_flag ? UserString(m_template_string) : m_template_string;
291
292 xpr::sregex var = '%' >> (xpr::s1 = -+xpr::_w) >> !(':' >> (xpr::s2 = -+xpr::_w)) >> '%';
293 m_text = xpr::regex_replace(template_str, var, Substitute(m_variables, m_validated));
294 }
295