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